Files
mev-beta/vendor/github.com/consensys/gnark-crypto/ecc/bls12-381/pairing.go

580 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2020-2025 Consensys Software Inc.
// Licensed under the Apache License, Version 2.0. See the LICENSE file for details.
package bls12381
import (
"errors"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
"github.com/consensys/gnark-crypto/ecc/bls12-381/internal/fptower"
)
// GT target group of the pairing
type GT = fptower.E12
type lineEvaluation struct {
r0 fptower.E2
r1 fptower.E2
r2 fptower.E2
}
// Pair calculates the reduced pairing for a set of points
// ∏ᵢ e(Pᵢ, Qᵢ).
//
// This function doesn't check that the inputs are in the correct subgroup. See IsInSubGroup.
func Pair(P []G1Affine, Q []G2Affine) (GT, error) {
f, err := MillerLoop(P, Q)
if err != nil {
return GT{}, err
}
return FinalExponentiation(&f), nil
}
// PairingCheck calculates the reduced pairing for a set of points and returns True if the result is One
// ∏ᵢ e(Pᵢ, Qᵢ) =? 1
//
// This function doesn't check that the inputs are in the correct subgroup. See IsInSubGroup.
func PairingCheck(P []G1Affine, Q []G2Affine) (bool, error) {
f, err := Pair(P, Q)
if err != nil {
return false, err
}
var one GT
one.SetOne()
return f.Equal(&one), nil
}
// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ
// where d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r
// we use instead d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r
// where s is the cofactor 3 (Hayashida et al.)
func FinalExponentiation(z *GT, _z ...*GT) GT {
var result GT
result.Set(z)
for _, e := range _z {
result.Mul(&result, e)
}
var t [3]GT
// Easy part
// (p⁶-1)(p²+1)
t[0].Conjugate(&result)
result.Inverse(&result)
t[0].Mul(&t[0], &result)
result.FrobeniusSquare(&t[0]).
Mul(&result, &t[0])
var one GT
one.SetOne()
if result.Equal(&one) {
return result
}
// Hard part (up to permutation)
// Daiki Hayashida, Kenichiro Hayasaka and Tadanori Teruya
// https://eprint.iacr.org/2020/875.pdf
t[0].CyclotomicSquare(&result)
t[1].ExptHalf(&t[0])
t[2].InverseUnitary(&result)
t[1].Mul(&t[1], &t[2])
t[2].Expt(&t[1])
t[1].InverseUnitary(&t[1])
t[1].Mul(&t[1], &t[2])
t[2].Expt(&t[1])
t[1].Frobenius(&t[1])
t[1].Mul(&t[1], &t[2])
result.Mul(&result, &t[0])
t[0].Expt(&t[1])
t[2].Expt(&t[0])
t[0].FrobeniusSquare(&t[1])
t[1].InverseUnitary(&t[1])
t[1].Mul(&t[1], &t[2])
t[1].Mul(&t[1], &t[0])
result.Mul(&result, &t[1])
return result
}
// MillerLoop computes the multi-Miller loop
// ∏ᵢ MillerLoop(Pᵢ, Qᵢ) = ∏ᵢ { fᵢ_{x,Qᵢ}(Pᵢ) }
func MillerLoop(P []G1Affine, Q []G2Affine) (GT, error) {
// check input size match
n := len(P)
if n == 0 || n != len(Q) {
return GT{}, errors.New("invalid inputs sizes")
}
// filter infinity points
p := make([]G1Affine, 0, n)
q := make([]G2Affine, 0, n)
for k := 0; k < n; k++ {
if P[k].IsInfinity() || Q[k].IsInfinity() {
continue
}
p = append(p, P[k])
q = append(q, Q[k])
}
n = len(p)
// projective points for Q
qProj := make([]g2Proj, n)
for k := 0; k < n; k++ {
qProj[k].FromAffine(&q[k])
}
var result GT
result.SetOne()
var l1, l2 lineEvaluation
var prodLines [5]E2
// Compute ∏ᵢ { fᵢ_{x₀,Q}(P) }
if n >= 1 {
// i = 62, separately to avoid an E12 Square
// (Square(res) = 1² = 1)
// LoopCounter[62] = 1
// k = 0, separately to avoid MulBy014 (res × )
// (assign line to res)
// qProj[0] ← 2qProj[0] and l1 the tangent passing 2qProj[0]
qProj[0].doubleStep(&l1)
// line evaluation at P[0] (assign)
result.C0.B0.Set(&l1.r0)
result.C0.B1.MulByElement(&l1.r1, &p[0].X)
result.C1.B1.MulByElement(&l1.r2, &p[0].Y)
// qProj[0] ← qProj[0]+Q[0] and
// l2 the line passing qProj[0] and Q[0]
qProj[0].addMixedStep(&l2, &q[0])
// line evaluation at P[0] (assign)
l2.r1.MulByElement(&l2.r1, &p[0].X)
l2.r2.MulByElement(&l2.r2, &p[0].Y)
// × res
prodLines = fptower.Mul014By014(&l2.r0, &l2.r1, &l2.r2, &result.C0.B0, &result.C0.B1, &result.C1.B1)
result.C0.B0 = prodLines[0]
result.C0.B1 = prodLines[1]
result.C0.B2 = prodLines[2]
result.C1.B1 = prodLines[3]
result.C1.B2 = prodLines[4]
}
// k >= 1
for k := 1; k < n; k++ {
// qProj[k] ← 2qProj[k] and l1 the tangent passing 2qProj[k]
qProj[k].doubleStep(&l1)
// line evaluation at P[k]
l1.r1.MulByElement(&l1.r1, &p[k].X)
l1.r2.MulByElement(&l1.r2, &p[k].Y)
// qProj[k] ← qProj[k]+Q[k] and
// l2 the line passing qProj[k] and Q[k]
qProj[k].addMixedStep(&l2, &q[k])
// line evaluation at P[k]
l2.r1.MulByElement(&l2.r1, &p[k].X)
l2.r2.MulByElement(&l2.r2, &p[k].Y)
// ×
prodLines = fptower.Mul014By014(&l2.r0, &l2.r1, &l2.r2, &l1.r0, &l1.r1, &l1.r2)
// ( × ) × result
result.MulBy01245(&prodLines)
}
// i <= 61
for i := len(LoopCounter) - 3; i >= 1; i-- {
// mutualize the square among n Miller loops
// (∏ᵢfᵢ)²
result.Square(&result)
for k := 0; k < n; k++ {
// qProj[k] ← 2qProj[k] and l1 the tangent passing 2qProj[k]
qProj[k].doubleStep(&l1)
// line evaluation at P[k]
l1.r1.MulByElement(&l1.r1, &p[k].X)
l1.r2.MulByElement(&l1.r2, &p[k].Y)
if LoopCounter[i] == 0 {
// × res
result.MulBy014(&l1.r0, &l1.r1, &l1.r2)
} else {
// qProj[k] ← qProj[k]+Q[k] and
// l2 the line passing qProj[k] and Q[k]
qProj[k].addMixedStep(&l2, &q[k])
// line evaluation at P[k]
l2.r1.MulByElement(&l2.r1, &p[k].X)
l2.r2.MulByElement(&l2.r2, &p[k].Y)
// ×
prodLines = fptower.Mul014By014(&l2.r0, &l2.r1, &l2.r2, &l1.r0, &l1.r1, &l1.r2)
// ( × ) × result
result.MulBy01245(&prodLines)
}
}
}
// i = 0, separately to avoid a point doubling
// LoopCounter[0] = 0
result.Square(&result)
for k := 0; k < n; k++ {
// l1 the tangent passing 2qProj[k]
qProj[k].tangentLine(&l1)
// line evaluation at P[k]
l1.r1.MulByElement(&l1.r1, &p[k].X)
l1.r2.MulByElement(&l1.r2, &p[k].Y)
// × result
result.MulBy014(&l1.r0, &l1.r1, &l1.r2)
}
// negative x₀
result.Conjugate(&result)
return result, nil
}
// doubleStep doubles a point in Homogenous projective coordinates, and evaluates the line in Miller loop
// https://eprint.iacr.org/2013/722.pdf (Section 4.3)
func (p *g2Proj) doubleStep(l *lineEvaluation) {
// get some Element from our pool
var t1, A, B, C, D, E, EE, F, G, H, I, J, K fptower.E2
A.Mul(&p.x, &p.y)
A.Halve()
B.Square(&p.y)
C.Square(&p.z)
D.Double(&C).
Add(&D, &C)
E.MulBybTwistCurveCoeff(&D)
F.Double(&E).
Add(&F, &E)
G.Add(&B, &F)
G.Halve()
H.Add(&p.y, &p.z).
Square(&H)
t1.Add(&B, &C)
H.Sub(&H, &t1)
I.Sub(&E, &B)
J.Square(&p.x)
EE.Square(&E)
K.Double(&EE).
Add(&K, &EE)
// X, Y, Z
p.x.Sub(&B, &F).
Mul(&p.x, &A)
p.y.Square(&G).
Sub(&p.y, &K)
p.z.Mul(&B, &H)
// Line evaluation
l.r0.Set(&I)
l.r1.Double(&J).
Add(&l.r1, &J)
l.r2.Neg(&H)
}
// addMixedStep point addition in Mixed Homogenous projective and Affine coordinates
// https://eprint.iacr.org/2013/722.pdf (Section 4.3)
func (p *g2Proj) addMixedStep(l *lineEvaluation, a *G2Affine) {
// get some Element from our pool
var Y2Z1, X2Z1, O, L, C, D, E, F, G, H, t0, t1, t2, J fptower.E2
Y2Z1.Mul(&a.Y, &p.z)
O.Sub(&p.y, &Y2Z1)
X2Z1.Mul(&a.X, &p.z)
L.Sub(&p.x, &X2Z1)
C.Square(&O)
D.Square(&L)
E.Mul(&L, &D)
F.Mul(&p.z, &C)
G.Mul(&p.x, &D)
t0.Double(&G)
H.Add(&E, &F).
Sub(&H, &t0)
t1.Mul(&p.y, &E)
// X, Y, Z
p.x.Mul(&L, &H)
p.y.Sub(&G, &H).
Mul(&p.y, &O).
Sub(&p.y, &t1)
p.z.Mul(&E, &p.z)
t2.Mul(&L, &a.Y)
J.Mul(&a.X, &O).
Sub(&J, &t2)
// Line evaluation
l.r0.Set(&J)
l.r1.Neg(&O)
l.r2.Set(&L)
}
// tangentCompute computes the tangent through [2]p in Homogenous projective coordinates.
// It does not compute the resulting point [2]p.
func (p *g2Proj) tangentLine(l *lineEvaluation) {
// get some Element from our pool
var t1, B, C, D, E, H, I, J fptower.E2
B.Square(&p.y)
C.Square(&p.z)
D.Double(&C).
Add(&D, &C)
E.MulBybTwistCurveCoeff(&D)
H.Add(&p.y, &p.z).
Square(&H)
t1.Add(&B, &C)
H.Sub(&H, &t1)
I.Sub(&E, &B)
J.Square(&p.x)
// Line evaluation
l.r0.Set(&I)
l.r1.Double(&J).
Add(&l.r1, &J)
l.r2.Neg(&H)
}
// ----------------------
// Fixed-argument pairing
// ----------------------
type LineEvaluationAff struct {
R0 fptower.E2
R1 fptower.E2
}
// PairFixedQ calculates the reduced pairing for a set of points
// ∏ᵢ e(Pᵢ, Qᵢ) where Q are fixed points in G2.
//
// This function doesn't check that the inputs are in the correct subgroup. See IsInSubGroup.
func PairFixedQ(P []G1Affine, lines [][2][len(LoopCounter) - 1]LineEvaluationAff) (GT, error) {
f, err := MillerLoopFixedQ(P, lines)
if err != nil {
return GT{}, err
}
return FinalExponentiation(&f), nil
}
// PairingCheckFixedQ calculates the reduced pairing for a set of points and returns True if the result is One
// ∏ᵢ e(Pᵢ, Qᵢ) =? 1 where Q are fixed points in G2.
//
// This function doesn't check that the inputs are in the correct subgroup. See IsInSubGroup.
func PairingCheckFixedQ(P []G1Affine, lines [][2][len(LoopCounter) - 1]LineEvaluationAff) (bool, error) {
f, err := PairFixedQ(P, lines)
if err != nil {
return false, err
}
var one GT
one.SetOne()
return f.Equal(&one), nil
}
// PrecomputeLines precomputes the lines for the fixed-argument Miller loop
func PrecomputeLines(Q G2Affine) (PrecomputedLines [2][len(LoopCounter) - 1]LineEvaluationAff) {
var accQ G2Affine
accQ.Set(&Q)
n := len(LoopCounter)
// i = n - 2
accQ.doubleStep(&PrecomputedLines[0][n-2])
accQ.addStep(&PrecomputedLines[1][n-2], &Q)
for i := n - 3; i >= 0; i-- {
if LoopCounter[i] == 0 {
accQ.doubleStep(&PrecomputedLines[0][i])
} else {
accQ.doubleAndAddStep(&PrecomputedLines[0][i], &PrecomputedLines[1][i], &Q)
}
}
return PrecomputedLines
}
// MillerLoopFixedQ computes the multi-Miller loop as in MillerLoop
// but Qᵢ are fixed points in G2 known in advance.
func MillerLoopFixedQ(P []G1Affine, lines [][2][len(LoopCounter) - 1]LineEvaluationAff) (GT, error) {
n := len(P)
if n == 0 || n != len(lines) {
return GT{}, errors.New("invalid inputs sizes")
}
// no need to filter infinity points:
// 1. if Pᵢ=(0,0) then -x/y=1/y=0 by gnark-crypto convention and so
// lines R0 and R1 are 0. It happens that result will stay, through
// the Miller loop, in 𝔽p⁶ because MulBy01(0,0,1),
// Mul01By01(0,0,1,0,0,1) and MulBy01245 set result.C0 to 0. At the
// end result will be in a proper subgroup of Fp¹² so it be reduced to
// 1 in FinalExponentiation.
//
// and/or
//
// 2. if Qᵢ=(0,0) then PrecomputeLines(Qᵢ) will return lines R0 and R1
// that are 0 because of gnark-convention (*/0==0) in doubleStep and
// addStep. Similarly to Pᵢ=(0,0) it happens that result be 1
// after the FinalExponentiation.
// precomputations
yInv := make([]fp.Element, n)
xNegOverY := make([]fp.Element, n)
for k := 0; k < n; k++ {
yInv[k].Set(&P[k].Y)
}
yInv = fp.BatchInvert(yInv)
for k := 0; k < n; k++ {
xNegOverY[k].Mul(&P[k].X, &yInv[k]).
Neg(&xNegOverY[k])
}
var result GT
result.SetOne()
var prodLines [5]E2
// Compute ∏ᵢ { fᵢ_{x₀,Q}(P) }
for i := len(LoopCounter) - 2; i >= 0; i-- {
// mutualize the square among n Miller loops
// (∏ᵢfᵢ)²
result.Square(&result)
for k := 0; k < n; k++ {
// line evaluation at P[k]
lines[k][0][i].R1.
MulByElement(
&lines[k][0][i].R1,
&yInv[k],
)
lines[k][0][i].R0.
MulByElement(&lines[k][0][i].R0,
&xNegOverY[k],
)
if LoopCounter[i] == 0 {
// × res
result.MulBy01(
&lines[k][0][i].R1,
&lines[k][0][i].R0,
)
} else {
lines[k][1][i].R1.
MulByElement(
&lines[k][1][i].R1,
&yInv[k],
)
lines[k][1][i].R0.
MulByElement(
&lines[k][1][i].R0,
&xNegOverY[k],
)
prodLines = fptower.Mul01By01(
&lines[k][0][i].R1, &lines[k][0][i].R0,
&lines[k][1][i].R1, &lines[k][1][i].R0,
)
result.MulBy01245(&prodLines)
}
}
}
// negative x₀
result.Conjugate(&result)
return result, nil
}
func (p *G2Affine) doubleStep(evaluations *LineEvaluationAff) {
var n, d, λ, xr, yr fptower.E2
// λ = 3x²/2y
n.Square(&p.X)
λ.Double(&n).
Add(&λ, &n)
d.Double(&p.Y)
λ.Div(&λ, &d)
// xr = λ²-2x
xr.Square(&λ).
Sub(&xr, &p.X).
Sub(&xr, &p.X)
// yr = λ(x-xr)-y
yr.Sub(&p.X, &xr).
Mul(&yr, &λ).
Sub(&yr, &p.Y)
evaluations.R0.Set(&λ)
evaluations.R1.Mul(&λ, &p.X).
Sub(&evaluations.R1, &p.Y)
p.X.Set(&xr)
p.Y.Set(&yr)
}
func (p *G2Affine) addStep(evaluations *LineEvaluationAff, a *G2Affine) {
var n, d, λ, λλ, xr, yr fptower.E2
// compute λ = (y2-y1)/(x2-x1)
n.Sub(&a.Y, &p.Y)
d.Sub(&a.X, &p.X)
λ.Div(&n, &d)
// xr = λ²-x1-x2
λλ.Square(&λ)
n.Add(&p.X, &a.X)
xr.Sub(&λλ, &n)
// yr = λ(x1-xr) - y1
yr.Sub(&p.X, &xr).
Mul(&yr, &λ).
Sub(&yr, &p.Y)
evaluations.R0.Set(&λ)
evaluations.R1.Mul(&λ, &p.X).
Sub(&evaluations.R1, &p.Y)
p.X.Set(&xr)
p.Y.Set(&yr)
}
func (p *G2Affine) doubleAndAddStep(evaluations1, evaluations2 *LineEvaluationAff, a *G2Affine) {
var n, d, l1, x3, l2, x4, y4 fptower.E2
// compute λ1 = (y2-y1)/(x2-x1)
n.Sub(&p.Y, &a.Y)
d.Sub(&p.X, &a.X)
l1.Div(&n, &d)
// compute x3 =λ1²-x1-x2
x3.Square(&l1)
x3.Sub(&x3, &p.X)
x3.Sub(&x3, &a.X)
// omit y3 computation
// compute line1
evaluations1.R0.Set(&l1)
evaluations1.R1.Mul(&l1, &p.X)
evaluations1.R1.Sub(&evaluations1.R1, &p.Y)
// compute λ2 = -λ1-2y1/(x3-x1)
n.Double(&p.Y)
d.Sub(&x3, &p.X)
l2.Div(&n, &d)
l2.Add(&l2, &l1)
l2.Neg(&l2)
// compute x4 = λ2²-x1-x3
x4.Square(&l2)
x4.Sub(&x4, &p.X)
x4.Sub(&x4, &x3)
// compute y4 = λ2(x1 - x4)-y1
y4.Sub(&p.X, &x4)
y4.Mul(&l2, &y4)
y4.Sub(&y4, &p.Y)
// compute line2
evaluations2.R0.Set(&l2)
evaluations2.R1.Mul(&l2, &p.X)
evaluations2.R1.Sub(&evaluations2.R1, &p.Y)
p.X.Set(&x4)
p.Y.Set(&y4)
}