580 lines
14 KiB
Go
580 lines
14 KiB
Go
// 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)
|
||
}
|