From e0d820e9c1060c743f97c4fadf6cb8357fa625a4 Mon Sep 17 00:00:00 2001 From: AlexandreBelling Date: Thu, 9 Jan 2025 17:32:08 +0100 Subject: [PATCH] Feat/adding gnark support for extension (#522) * initial implementaiton of the wrapper * started updating the gnark interpolation --- .../fft/fastpolyext/gnark_interpolation.go | 91 +++++ prover/maths/field/fext/e3.go | 252 ------------- prover/maths/field/fext/e3_test.go | 311 ---------------- prover/maths/field/fext/e4.go | 341 ------------------ prover/maths/field/fext/e4_test.go | 300 --------------- prover/maths/field/fext/gnarkfext/api.go | 183 ++++++++++ prover/maths/field/fext/utils.go | 21 -- 7 files changed, 274 insertions(+), 1225 deletions(-) create mode 100644 prover/maths/fft/fastpolyext/gnark_interpolation.go delete mode 100644 prover/maths/field/fext/e3.go delete mode 100644 prover/maths/field/fext/e3_test.go delete mode 100644 prover/maths/field/fext/e4.go delete mode 100644 prover/maths/field/fext/e4_test.go create mode 100644 prover/maths/field/fext/gnarkfext/api.go diff --git a/prover/maths/fft/fastpolyext/gnark_interpolation.go b/prover/maths/fft/fastpolyext/gnark_interpolation.go new file mode 100644 index 000000000..fd1c2018b --- /dev/null +++ b/prover/maths/fft/fastpolyext/gnark_interpolation.go @@ -0,0 +1,91 @@ +package fastpolyext + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/linea-monorepo/prover/maths/fft" + "github.com/consensys/linea-monorepo/prover/maths/field" + "github.com/consensys/linea-monorepo/prover/maths/field/fext/gnarkfext" + "github.com/consensys/linea-monorepo/prover/utils" + "github.com/consensys/linea-monorepo/prover/utils/gnarkutil" +) + +// Evaluate a polynomial in lagrange basis on a gnark circuit +func InterpolateGnark(api gnarkfext.API, poly []gnarkfext.E2, x gnarkfext.E2) frontend.Variable { + + if !utils.IsPowerOfTwo(len(poly)) { + utils.Panic("only support powers of two but poly has length %v", len(poly)) + } + + // When the poly is of length 1 it means it is a constant polynomial and its + // evaluation is trivial. + if len(poly) == 1 { + return poly[0] + } + + n := len(poly) + domain := fft.NewDomain(n) + one := field.One() + + // Test that x is not a root of unity. In the other case, we would + // have to divide by zero. In practice this constraint is not necessary + // (because the division constraint would be non-satisfiable anyway) + // But doing an explicit check clarifies the need. + xN := gnarkutil.Exp(api, x, n) + api.AssertIsDifferent(xN, 1) + + // Compute the term-wise summand of the interpolation formula. + // This will allow the gnark solver to process the expensive + // inverses in parallel. + terms := make([]gnarkfext.E2, n) + + // omegaMinI carries the domain's inverse root of unity generator raised to + // the power I in the following loop. It is initialized with omega**0 = 1. + omegaI := frontend.Variable(1) + + for i := 0; i < n; i++ { + + if i > 0 { + omegaI = api.Inner.Mul(omegaI, domain.GeneratorInv) + } + + xOmegaN := api.MulByBase(x, omegaI) + terms[i] = api.Sub(xOmegaN, gnarkfext.One()) + // No point doing a batch inverse in a circuit + terms[i] = api.Inverse(terms[i]) + terms[i] = api.Mul(terms[i], poly[i]) + } + + nonNilTerms := make([]frontend.Variable, 0, len(terms)) + for i := range terms { + if terms[i] == nil { + continue + } + + nonNilTerms = append(nonNilTerms, terms[i]) + } + + // Then sum all the terms + var res frontend.Variable + + switch { + case len(nonNilTerms) == 0: + res = 0 + case len(nonNilTerms) == 1: + res = nonNilTerms[0] + case len(nonNilTerms) == 2: + res = api.Add(nonNilTerms[0], nonNilTerms[1]) + default: + res = api.Add(nonNilTerms[0], nonNilTerms[1], nonNilTerms[2:]...) + } + + /* + Then multiply the res by a factor \frac{g^{1 - n}X^n -g}{n} + */ + factor := xN + factor = api.Sub(factor, one) + factor = api.Mul(factor, domain.CardinalityInv) + res = api.Mul(res, factor) + + return res + +} diff --git a/prover/maths/field/fext/e3.go b/prover/maths/field/fext/e3.go deleted file mode 100644 index 44ae8c155..000000000 --- a/prover/maths/field/fext/e3.go +++ /dev/null @@ -1,252 +0,0 @@ -package fext - -import ( - "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" -) - -// E3 is a degree-three finite field extension of fr3 -type E3 struct { - A0, A1, A2 fr.Element -} - -// Equal returns true if z equals x, false otherwise -// note this is more efficient than calling "z == x" -func (z *E3) Equal(x *E3) bool { - return z.A0.Equal(&x.A0) && z.A1.Equal(&x.A1) && z.A2.Equal(&x.A2) -} - -// SetString sets a E3 elmt from string -func (z *E3) SetString(s1, s2, s3 string) *E3 { - z.A0.SetString(s1) - z.A1.SetString(s2) - z.A2.SetString(s3) - return z -} - -// SetZero sets an E3 elmt to zero -func (z *E3) SetZero() *E3 { - *z = E3{} - return z -} - -// Clone returns a copy of self -func (z *E3) Clone() *E3 { - return &E3{ - A0: z.A0, - A1: z.A1, - A2: z.A2, - } -} - -// Set Sets a E3 elmt form another E3 elmt -func (z *E3) Set(x *E3) *E3 { - *z = *x - return z -} - -// SetOne sets z to 1 in Montgomery form and returns z -func (z *E3) SetOne() *E3 { - z.A0.SetOne() - z.A1.SetZero() - z.A2.SetZero() - return z -} - -// SetRandom sets z to a random elmt -func (z *E3) SetRandom() (*E3, error) { - if _, err := z.A0.SetRandom(); err != nil { - return nil, err - } - if _, err := z.A1.SetRandom(); err != nil { - return nil, err - } - if _, err := z.A2.SetRandom(); err != nil { - return nil, err - } - return z, nil -} - -// IsZero returns true if z is zero, false otherwise -func (z *E3) IsZero() bool { - return z.A0.IsZero() && z.A1.IsZero() && z.A2.IsZero() -} - -// IsOne returns true if z is one, false otherwise -func (z *E3) IsOne() bool { - return z.A0.IsOne() && z.A1.IsZero() && z.A2.IsZero() -} - -// Neg negates the E3 number -func (z *E3) Neg(x *E3) *E3 { - z.A0.Neg(&x.A0) - z.A1.Neg(&x.A1) - z.A2.Neg(&x.A2) - return z -} - -// Add adds two elements of E3 -func (z *E3) Add(x, y *E3) *E3 { - z.A0.Add(&x.A0, &y.A0) - z.A1.Add(&x.A1, &y.A1) - z.A2.Add(&x.A2, &y.A2) - return z -} - -// Sub subtracts two elements of E3 -func (z *E3) Sub(x, y *E3) *E3 { - z.A0.Sub(&x.A0, &y.A0) - z.A1.Sub(&x.A1, &y.A1) - z.A2.Sub(&x.A2, &y.A2) - return z -} - -// Double doubles an element in E3 -func (z *E3) Double(x *E3) *E3 { - z.A0.Double(&x.A0) - z.A1.Double(&x.A1) - z.A2.Double(&x.A2) - return z -} - -// String puts E3 elmt in string form -func (z *E3) String() string { - return (z.A0.String() + "+(" + z.A1.String() + ")*u+(" + z.A2.String() + ")*u**2") -} - -// MulByElement multiplies an element in E3 by an element in fr -func (z *E3) MulByElement(x *E3, y *fr.Element) *E3 { - _y := *y - z.A0.Mul(&x.A0, &_y) - z.A1.Mul(&x.A1, &_y) - z.A2.Mul(&x.A2, &_y) - return z -} - -// Mul sets z to the E3-product of x,y, returns z -func (z *E3) Mul(x, y *E3) *E3 { - // Karatsuba method for cubic extensions - // https://eprint.iacr.org/2006/471.pdf (section 4) - var t0, t1, t2, c0, c1, c2, tmp fr.Element - t0.Mul(&x.A0, &y.A0) - t1.Mul(&x.A1, &y.A1) - t2.Mul(&x.A2, &y.A2) - - c0.Add(&x.A1, &x.A2) - tmp.Add(&y.A1, &y.A2) - c0.Mul(&c0, &tmp).Sub(&c0, &t1).Sub(&t2, &c0) - - tmp.Add(&x.A0, &x.A2) - c2.Add(&y.A0, &y.A2).Mul(&c2, &tmp).Sub(&c2, &t0).Sub(&c2, &t2) - - c1.Add(&x.A0, &x.A1) - tmp.Add(&y.A0, &y.A1) - c1.Mul(&c1, &tmp).Sub(&c1, &t0).Sub(&c1, &t1) - - z.A0.Add(&c0, &t0) - z.A1.Sub(&c1, &t2) - z.A2.Add(&c2, &t1) - - return z -} - -// MulAssign sets z to the E3-product of z,y, returns z -func (z *E3) MulAssign(x *E3) *E3 { - return z.Mul(z, x) -} - -// Square sets z to the E3-product of x,x, returns z -func (z *E3) Square(x *E3) *E3 { - - // Algorithm 16 from https://eprint.iacr.org/2010/354.pdf - var c4, c5, c1, c2, c3, c6 fr.Element - - c6.Double(&x.A1) - c4.Mul(&x.A0, &c6) // x.A0 * xA1 * 2 - c5.Square(&x.A2) - c1.Sub(&c4, &c5) - c2.Sub(&c4, &c5) - - c3.Square(&x.A0) - c4.Sub(&x.A0, &x.A1).Add(&c4, &x.A2) - c5.Mul(&c6, &x.A2) // x.A1 * xA2 * 2 - c4.Square(&c4) - c4.Add(&c4, &c5).Sub(&c4, &c3) - - z.A0.Sub(&c3, &c5) - z.A1 = c1 - z.A2.Add(&c2, &c4) - - return z -} - -// MulByNonResidue mul x by (0,1,0) -func (z *E3) MulByNonResidue(x *E3) *E3 { - z.A2, z.A1, z.A0 = x.A1, x.A0, x.A2 - z.A0.Neg(&z.A0) - return z -} - -// Inverse an element in E3 -// -// if x == 0, sets and returns z = x -func (z *E3) Inverse(x *E3) *E3 { - // Algorithm 17 from https://eprint.iacr.org/2010/354.pdf - // step 9 is wrong in the paper it's t1-t4 - var t0, t1, t2, t3, t4, t5, t6, c0, c1, c2, d1, d2 fr.Element - t0.Square(&x.A0) - t1.Square(&x.A1) - t2.Square(&x.A2) - t3.Mul(&x.A0, &x.A1) - t4.Mul(&x.A0, &x.A2) - t5.Mul(&x.A1, &x.A2) - c0.Add(&t5, &t0) - c1.Neg(&t2).Sub(&c1, &t3) - c2.Sub(&t1, &t4) - t6.Mul(&x.A0, &c0) - d1.Mul(&x.A2, &c1) - d2.Mul(&x.A1, &c2) - d1.Add(&d1, &d2) - t6.Sub(&t6, &d1) - t6.Inverse(&t6) - z.A0.Mul(&c0, &t6) - z.A1.Mul(&c1, &t6) - z.A2.Mul(&c2, &t6) - - return z -} - -// BatchInvertE3 returns a new slice with every element in a inverted. -// It uses Montgomery batch inversion trick. -// -// if a[i] == 0, returns result[i] = a[i] -func BatchInvertE3(a []E3) []E3 { - res := make([]E3, len(a)) - if len(a) == 0 { - return res - } - - zeroes := make([]bool, len(a)) - var accumulator E3 - accumulator.SetOne() - - for i := 0; i < len(a); i++ { - if a[i].IsZero() { - zeroes[i] = true - continue - } - res[i].Set(&accumulator) - accumulator.Mul(&accumulator, &a[i]) - } - - accumulator.Inverse(&accumulator) - - for i := len(a) - 1; i >= 0; i-- { - if zeroes[i] { - continue - } - res[i].Mul(&res[i], &accumulator) - accumulator.Mul(&accumulator, &a[i]) - } - - return res -} diff --git a/prover/maths/field/fext/e3_test.go b/prover/maths/field/fext/e3_test.go deleted file mode 100644 index e115bc7ec..000000000 --- a/prover/maths/field/fext/e3_test.go +++ /dev/null @@ -1,311 +0,0 @@ -package fext - -import ( - "testing" - - "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" - "github.com/leanovate/gopter" - "github.com/leanovate/gopter/prop" -) - -// ------------------------------------------------------------ -// tests - -func TestE3ReceiverIsOperand(t *testing.T) { - - parameters := gopter.DefaultTestParameters() - parameters.MinSuccessfulTests = 100 - - properties := gopter.NewProperties(parameters) - - genA := GenE3() - genB := GenE3() - genfr := GenFr() - - properties.Property("[BLS12-377] Having the receiver as operand (addition) should output the same result", prop.ForAll( - func(a, b *E3) bool { - var c, d E3 - d.Set(a) - c.Add(a, b) - a.Add(a, b) - b.Add(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (sub) should output the same result", prop.ForAll( - func(a, b *E3) bool { - var c, d E3 - d.Set(a) - c.Sub(a, b) - a.Sub(a, b) - b.Sub(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (mul) should output the same result", prop.ForAll( - func(a, b *E3) bool { - var c, d E3 - d.Set(a) - c.Mul(a, b) - a.Mul(a, b) - b.Mul(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (square) should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Square(a) - a.Square(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (neg) should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Neg(a) - a.Neg(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (double) should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Double(a) - a.Double(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (mul by non residue) should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - b.MulByNonResidue(a) - a.MulByNonResidue(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (Inverse) should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Inverse(a) - a.Inverse(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (mul by element) should output the same result", prop.ForAll( - func(a *E3, b fr.Element) bool { - var c E3 - c.MulByElement(a, &b) - a.MulByElement(a, &b) - return a.Equal(&c) - }, - genA, - genfr, - )) - - properties.TestingRun(t, gopter.ConsoleReporter(false)) -} - -func TestE3Ops(t *testing.T) { - - parameters := gopter.DefaultTestParameters() - parameters.MinSuccessfulTests = 100 - - properties := gopter.NewProperties(parameters) - - genA := GenE3() - genB := GenE3() - genfr := GenFr() - - properties.Property("[BLS12-377] sub & add should leave an element invariant", prop.ForAll( - func(a, b *E3) bool { - var c E3 - c.Set(a) - c.Add(&c, b).Sub(&c, b) - return c.Equal(a) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] mul & inverse should leave an element invariant", prop.ForAll( - func(a, b *E3) bool { - var c, d E3 - d.Inverse(b) - c.Set(a) - c.Mul(&c, b).Mul(&c, &d) - return c.Equal(a) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] inverse twice should leave an element invariant", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Inverse(a).Inverse(&b) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] BatchInvertE3 should output the same result as Inverse", prop.ForAll( - func(a, b, c *E3) bool { - - batch := BatchInvertE3([]E3{*a, *b, *c}) - a.Inverse(a) - b.Inverse(b) - c.Inverse(c) - return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) - }, - genA, - genA, - genA, - )) - - properties.Property("[BLS12-377] neg twice should leave an element invariant", prop.ForAll( - func(a *E3) bool { - var b E3 - b.Neg(a).Neg(&b) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] square and mul should output the same result", prop.ForAll( - func(a *E3) bool { - var b, c E3 - b.Mul(a, a) - c.Square(a) - return b.Equal(&c) - }, - genA, - )) - - properties.Property("[BLS12-377] MulByElement MulByElement inverse should leave an element invariant", prop.ForAll( - func(a *E3, b fr.Element) bool { - var c E3 - var d fr.Element - d.Inverse(&b) - c.MulByElement(a, &b).MulByElement(&c, &d) - return c.Equal(a) - }, - genA, - genfr, - )) - - properties.Property("[BLS12-377] Double and mul by 2 should output the same result", prop.ForAll( - func(a *E3) bool { - var b E3 - var c fr.Element - c.SetUint64(2) - b.Double(a) - a.MulByElement(a, &c) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Mulbynonres should be the same as multiplying by (0,1)", prop.ForAll( - func(a *E3) bool { - var b, c, d E3 - b.A1.SetOne() - c.MulByNonResidue(a) - d.Mul(a, &b) - return c.Equal(&d) - }, - genA, - )) - - properties.TestingRun(t, gopter.ConsoleReporter(false)) -} - -// ------------------------------------------------------------ -// benches - -func BenchmarkE3Add(b *testing.B) { - var a, c E3 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Add(&a, &c) - } -} - -func BenchmarkE3Sub(b *testing.B) { - var a, c E3 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Sub(&a, &c) - } -} - -func BenchmarkE3Mul(b *testing.B) { - var a, c E3 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Mul(&a, &c) - } -} - -func BenchmarkE3MulByElement(b *testing.B) { - var a E3 - var c fr.Element - _, _ = c.SetRandom() - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.MulByElement(&a, &c) - } -} - -func BenchmarkE3Square(b *testing.B) { - var a E3 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Square(&a) - } -} - -func BenchmarkE3Inverse(b *testing.B) { - var a E3 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Inverse(&a) - } -} - -func BenchmarkE3MulNonRes(b *testing.B) { - var a E3 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.MulByNonResidue(&a) - } -} diff --git a/prover/maths/field/fext/e4.go b/prover/maths/field/fext/e4.go deleted file mode 100644 index 9b6b6ffc4..000000000 --- a/prover/maths/field/fext/e4.go +++ /dev/null @@ -1,341 +0,0 @@ -package fext - -import ( - "math/big" - - "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" -) - -// E4 is a degree two finite field extension of fr2 -type E4 struct { - B0, B1 E2 -} - -// Equal returns true if z equals x, false otherwise -func (z *E4) Equal(x *E4) bool { - return z.B0.Equal(&x.B0) && z.B1.Equal(&x.B1) -} - -// Cmp compares (lexicographic order) z and x and returns: -// -// -1 if z < x -// 0 if z == x -// +1 if z > x -func (z *E4) Cmp(x *E4) int { - if a1 := z.B1.Cmp(&x.B1); a1 != 0 { - return a1 - } - return z.B0.Cmp(&x.B0) -} - -// LexicographicallyLargest returns true if this element is strictly lexicographically -// larger than its negation, false otherwise -func (z *E4) LexicographicallyLargest() bool { - // adapted from github.com/zkcrypto/bls12_381 - if z.B1.IsZero() { - return z.B0.LexicographicallyLargest() - } - return z.B1.LexicographicallyLargest() -} - -// String puts E4 in string form -func (z *E4) String() string { - return (z.B0.String() + "+(" + z.B1.String() + ")*v") -} - -// SetString sets a E4 from string -func (z *E4) SetString(s0, s1, s2, s3 string) *E4 { - z.B0.SetString(s0, s1) - z.B1.SetString(s2, s3) - return z -} - -// Set copies x into z and returns z -func (z *E4) Set(x *E4) *E4 { - z.B0 = x.B0 - z.B1 = x.B1 - return z -} - -// SetZero sets an E4 elmt to zero -func (z *E4) SetZero() *E4 { - z.B0.SetZero() - z.B1.SetZero() - return z -} - -// SetOne sets z to 1 in Montgomery form and returns z -func (z *E4) SetOne() *E4 { - *z = E4{} - z.B0.A0.SetOne() - return z -} - -// MulByElement multiplies an element in E4 by an element in fr -func (z *E4) MulByElement(x *E4, y *fr.Element) *E4 { - var yCopy fr.Element - yCopy.Set(y) - z.B0.MulByElement(&x.B0, &yCopy) - z.B1.MulByElement(&x.B1, &yCopy) - return z -} - -// MulByE2 multiplies an element in E4 by an element in E2 -func (z *E4) MulByE2(x *E4, y *E2) *E4 { - var yCopy E2 - yCopy.Set(y) - z.B0.Mul(&x.B0, &yCopy) - z.B1.Mul(&x.B1, &yCopy) - return z -} - -// Add sets z=x+y in E4 and returns z -func (z *E4) Add(x, y *E4) *E4 { - z.B0.Add(&x.B0, &y.B0) - z.B1.Add(&x.B1, &y.B1) - return z -} - -// Sub sets z to x-y and returns z -func (z *E4) Sub(x, y *E4) *E4 { - z.B0.Sub(&x.B0, &y.B0) - z.B1.Sub(&x.B1, &y.B1) - return z -} - -// Double sets z=2*x and returns z -func (z *E4) Double(x *E4) *E4 { - z.B0.Double(&x.B0) - z.B1.Double(&x.B1) - return z -} - -// Neg negates an E4 element -func (z *E4) Neg(x *E4) *E4 { - z.B0.Neg(&x.B0) - z.B1.Neg(&x.B1) - return z -} - -// SetRandom used only in tests -func (z *E4) SetRandom() (*E4, error) { - if _, err := z.B0.SetRandom(); err != nil { - return nil, err - } - if _, err := z.B1.SetRandom(); err != nil { - return nil, err - } - return z, nil -} - -// IsZero returns true if z is zero, false otherwise -func (z *E4) IsZero() bool { - return z.B0.IsZero() && z.B1.IsZero() -} - -// IsOne returns true if z is one, false otherwise -func (z *E4) IsOne() bool { - return z.B0.IsOne() && z.B1.IsZero() -} - -// MulByNonResidue mul x by (0,1) -func (z *E4) MulByNonResidue(x *E4) *E4 { - z.B1, z.B0 = x.B0, x.B1 - z.B0.MulByNonResidue(&z.B0) - return z -} - -// Mul sets z=x*y in E4 and returns z -func (z *E4) Mul(x, y *E4) *E4 { - var a, b, c E2 - a.Add(&x.B0, &x.B1) - b.Add(&y.B0, &y.B1) - a.Mul(&a, &b) - b.Mul(&x.B0, &y.B0) - c.Mul(&x.B1, &y.B1) - z.B1.Sub(&a, &b).Sub(&z.B1, &c) - z.B0.MulByNonResidue(&c).Add(&z.B0, &b) - return z -} - -// Square sets z=x*x in E4 and returns z -func (z *E4) Square(x *E4) *E4 { - - //Algorithm 22 from https://eprint.iacr.org/2010/354.pdf - var c0, c2, c3 E2 - c0.Sub(&x.B0, &x.B1) - c3.MulByNonResidue(&x.B1).Sub(&x.B0, &c3) - c2.Mul(&x.B0, &x.B1) - c0.Mul(&c0, &c3).Add(&c0, &c2) - z.B1.Double(&c2) - c2.MulByNonResidue(&c2) - z.B0.Add(&c0, &c2) - - return z -} - -// Inverse sets z to the inverse of x in E4 and returns z -// -// if x == 0, sets and returns z = x -func (z *E4) Inverse(x *E4) *E4 { - // Algorithm 23 from https://eprint.iacr.org/2010/354.pdf - - var t0, t1, tmp E2 - t0.Square(&x.B0) - t1.Square(&x.B1) - tmp.MulByNonResidue(&t1) - t0.Sub(&t0, &tmp) - t1.Inverse(&t0) - z.B0.Mul(&x.B0, &t1) - z.B1.Mul(&x.B1, &t1).Neg(&z.B1) - - return z -} - -// Exp sets z=xᵏ (mod q⁴) and returns it -func (z *E4) Exp(x E4, k *big.Int) *E4 { - if k.IsUint64() && k.Uint64() == 0 { - return z.SetOne() - } - - e := k - if k.Sign() == -1 { - // negative k, we invert - // if k < 0: xᵏ (mod q⁴) == (x⁻¹)ᵏ (mod q⁴) - x.Inverse(&x) - - // we negate k in a temp big.Int since - // Int.Bit(_) of k and -k is different - e = bigIntPool.Get().(*big.Int) - defer bigIntPool.Put(e) - e.Neg(k) - } - - z.SetOne() - b := e.Bytes() - for i := 0; i < len(b); i++ { - w := b[i] - for j := 0; j < 8; j++ { - z.Square(z) - if (w & (0b10000000 >> j)) != 0 { - z.Mul(z, &x) - } - } - } - - return z -} - -// Conjugate sets z to x conjugated and returns z -func (z *E4) Conjugate(x *E4) *E4 { - z.B0 = x.B0 - z.B1.Neg(&x.B1) - return z -} - -func (z *E4) Halve() { - - z.B0.A0.Halve() - z.B0.A1.Halve() - z.B1.A0.Halve() - z.B1.A1.Halve() -} - -// norm sets x to the norm of z -func (z *E4) norm(x *E2) { - var tmp E2 - tmp.Square(&z.B1).MulByNonResidue(&tmp) - x.Square(&z.B0).Sub(x, &tmp) -} - -// Legendre returns the Legendre symbol of z -func (z *E4) Legendre() int { - var n E2 - z.norm(&n) - return n.Legendre() -} - -// Sqrt sets z to the square root of and returns z -// The function does not test whether the square root -// exists or not, it's up to the caller to call -// Legendre beforehand. -// cf https://eprint.iacr.org/2012/685.pdf (algo 10) -func (z *E4) Sqrt(x *E4) *E4 { - - // precomputation - var b, c, d, e, f, x0, _g E4 - var _b, o E2 - - // c must be a non square (works for p=1 mod 12 hence 1 mod 4, only bls377 has such a p currently) - c.B1.SetOne() - - q := fr.Modulus() - var exp, one big.Int - one.SetUint64(1) - exp.Mul(q, q).Sub(&exp, &one).Rsh(&exp, 1) - d.Exp(c, &exp) - e.Mul(&d, &c).Inverse(&e) - f.Mul(&d, &c).Square(&f) - - // computation - exp.Rsh(&exp, 1) - b.Exp(*x, &exp) - b.norm(&_b) - o.SetOne() - if _b.Equal(&o) { - x0.Square(&b).Mul(&x0, x) - _b.Set(&x0.B0).Sqrt(&_b) - _g.B0.Set(&_b) - z.Conjugate(&b).Mul(z, &_g) - return z - } - x0.Square(&b).Mul(&x0, x).Mul(&x0, &f) - _b.Set(&x0.B0).Sqrt(&_b) - _g.B0.Set(&_b) - z.Conjugate(&b).Mul(z, &_g).Mul(z, &e) - - return z -} - -// BatchInvertE4 returns a new slice with every element in a inverted. -// It uses Montgomery batch inversion trick. -// -// if a[i] == 0, returns result[i] = a[i] -func BatchInvertE4(a []E4) []E4 { - res := make([]E4, len(a)) - if len(a) == 0 { - return res - } - - zeroes := make([]bool, len(a)) - var accumulator E4 - accumulator.SetOne() - - for i := 0; i < len(a); i++ { - if a[i].IsZero() { - zeroes[i] = true - continue - } - res[i].Set(&accumulator) - accumulator.Mul(&accumulator, &a[i]) - } - - accumulator.Inverse(&accumulator) - - for i := len(a) - 1; i >= 0; i-- { - if zeroes[i] { - continue - } - res[i].Mul(&res[i], &accumulator) - accumulator.Mul(&accumulator, &a[i]) - } - - return res -} - -// Div divides an element in E4 by an element in E4 -func (z *E4) Div(x *E4, y *E4) *E4 { - var r E4 - r.Inverse(y).Mul(x, &r) - return z.Set(&r) -} diff --git a/prover/maths/field/fext/e4_test.go b/prover/maths/field/fext/e4_test.go deleted file mode 100644 index 66a305c86..000000000 --- a/prover/maths/field/fext/e4_test.go +++ /dev/null @@ -1,300 +0,0 @@ -package fext - -import ( - "testing" - - "github.com/leanovate/gopter" - "github.com/leanovate/gopter/prop" -) - -// ------------------------------------------------------------ -// tests - -func TestE4ReceiverIsOperand(t *testing.T) { - - parameters := gopter.DefaultTestParameters() - parameters.MinSuccessfulTests = 100 - - properties := gopter.NewProperties(parameters) - - genA := GenE4() - genB := GenE4() - - properties.Property("[BLS12-377] Having the receiver as operand (addition) should output the same result", prop.ForAll( - func(a, b *E4) bool { - var c, d E4 - d.Set(a) - c.Add(a, b) - a.Add(a, b) - b.Add(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (sub) should output the same result", prop.ForAll( - func(a, b *E4) bool { - var c, d E4 - d.Set(a) - c.Sub(a, b) - a.Sub(a, b) - b.Sub(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (mul) should output the same result", prop.ForAll( - func(a, b *E4) bool { - var c, d E4 - d.Set(a) - c.Mul(a, b) - a.Mul(a, b) - b.Mul(&d, b) - return a.Equal(b) && a.Equal(&c) && b.Equal(&c) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (square) should output the same result", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Square(a) - a.Square(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (double) should output the same result", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Double(a) - a.Double(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (mul by non residue) should output the same result", prop.ForAll( - func(a *E4) bool { - var b E4 - b.MulByNonResidue(a) - a.MulByNonResidue(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (Inverse) should output the same result", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Inverse(a) - a.Inverse(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (Conjugate) should output the same result", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Conjugate(a) - a.Conjugate(a) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] Having the receiver as operand (Sqrt) should output the same result", prop.ForAll( - func(a *E4) bool { - var b, c, d, s E4 - - s.Square(a) - a.Set(&s) - b.Set(&s) - - a.Sqrt(a) - b.Sqrt(&b) - - c.Square(a) - d.Square(&b) - return c.Equal(&d) - }, - genA, - )) - - properties.TestingRun(t, gopter.ConsoleReporter(false)) -} - -func TestE4Ops(t *testing.T) { - - parameters := gopter.DefaultTestParameters() - parameters.MinSuccessfulTests = 100 - - properties := gopter.NewProperties(parameters) - - genA := GenE4() - genB := GenE4() - - properties.Property("[BLS12-377] sub & add should leave an element invariant", prop.ForAll( - func(a, b *E4) bool { - var c E4 - c.Set(a) - c.Add(&c, b).Sub(&c, b) - return c.Equal(a) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] mul & inverse should leave an element invariant", prop.ForAll( - func(a, b *E4) bool { - var c, d E4 - d.Inverse(b) - c.Set(a) - c.Mul(&c, b).Mul(&c, &d) - return c.Equal(a) - }, - genA, - genB, - )) - - properties.Property("[BLS12-377] BatchInvertE4 should output the same result as Inverse", prop.ForAll( - func(a, b, c *E4) bool { - - batch := BatchInvertE4([]E4{*a, *b, *c}) - a.Inverse(a) - b.Inverse(b) - c.Inverse(c) - return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) - }, - genA, - genA, - genB, - )) - - properties.Property("[BLS12-377] inverse twice should leave an element invariant", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Inverse(a).Inverse(&b) - return a.Equal(&b) - }, - genA, - )) - - properties.Property("[BLS12-377] square and mul should output the same result", prop.ForAll( - func(a *E4) bool { - var b, c E4 - b.Mul(a, a) - c.Square(a) - return b.Equal(&c) - }, - genA, - )) - - properties.Property("[BLS12-377] Legendre on square should output 1", prop.ForAll( - func(a *E4) bool { - var b E4 - b.Square(a) - c := b.Legendre() - return c == 1 - }, - genA, - )) - - properties.Property("[BLS12-377] square(sqrt) should leave an element invariant", prop.ForAll( - func(a *E4) bool { - var b, c, d, e E4 - b.Square(a) - c.Sqrt(&b) - d.Square(&c) - e.Neg(a) - return (c.Equal(a) || c.Equal(&e)) && d.Equal(&b) - }, - genA, - )) - - properties.TestingRun(t, gopter.ConsoleReporter(false)) -} - -// ------------------------------------------------------------ -// benches - -func BenchmarkE4Add(b *testing.B) { - var a, c E4 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Add(&a, &c) - } -} - -func BenchmarkE4Sub(b *testing.B) { - var a, c E4 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Sub(&a, &c) - } -} - -func BenchmarkE4Mul(b *testing.B) { - var a, c E4 - _, _ = a.SetRandom() - _, _ = c.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Mul(&a, &c) - } -} - -func BenchmarkE4Square(b *testing.B) { - var a E4 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Square(&a) - } -} - -func BenchmarkE4Sqrt(b *testing.B) { - var a E4 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Sqrt(&a) - } -} - -func BenchmarkE4Inverse(b *testing.B) { - var a E4 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Inverse(&a) - } -} - -func BenchmarkE4MulNonRes(b *testing.B) { - var a E4 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.MulByNonResidue(&a) - } -} - -func BenchmarkE4Conjugate(b *testing.B) { - var a E4 - _, _ = a.SetRandom() - b.ResetTimer() - for i := 0; i < b.N; i++ { - a.Conjugate(&a) - } -} diff --git a/prover/maths/field/fext/gnarkfext/api.go b/prover/maths/field/fext/gnarkfext/api.go new file mode 100644 index 000000000..ac63644ee --- /dev/null +++ b/prover/maths/field/fext/gnarkfext/api.go @@ -0,0 +1,183 @@ +package gnarkfext + +import ( + "github.com/consensys/gnark/frontend" + "github.com/consensys/linea-monorepo/prover/maths/field/fext" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" +) + +// API is a wrapper of [frontend.API] with methods specialized for field +// extension operations. +type API struct { + Inner frontend.API +} + +// E2 element in a quadratic extension +type E2 struct { + A0, A1 frontend.Variable +} + +func NewZero() E2 { + return E2{ + A0: 0, + A1: 0, + } +} + +// SetOne returns a newly allocated element equal to 1 +func One() E2 { + return E2{ + A0: 1, + A1: 0, + } +} + +// IsZero returns 1 if the element is equal to 0 and 0 otherwise +func (api *API) IsZero(e E2) frontend.Variable { + return api.Inner.And(api.Inner.IsZero(e.A0), api.Inner.IsZero(e.A1)) +} + +// Neg negates a e2 elmt +func (api *API) Neg(e1 E2) E2 { + return E2{ + A0: api.Inner.Sub(0, e1.A0), + A1: api.Inner.Sub(0, e1.A1), + } +} + +// Add e2 elmts +func (api *API) Add(e1, e2 E2) E2 { + return E2{ + A0: api.Inner.Add(e1.A0, e2.A0), + A1: api.Inner.Add(e1.A1, e2.A1), + } +} + +// Double e2 elmt +func (api *API) Double(e1 E2) E2 { + return E2{ + A0: api.Inner.Mul(e1.A0, 2), + A1: api.Inner.Mul(e1.A1, 2), + } +} + +// Sub e2 elmts +func (api *API) Sub(e1, e2 E2) E2 { + return E2{ + A0: api.Inner.Sub(e1.A0, e2.A0), + A1: api.Inner.Sub(e1.A1, e2.A1), + } +} + +// Mul e2 elmts +func (api *API) Mul(x, y E2) E2 { + + a := api.Inner.Add(x.A0, x.A1) + b := api.Inner.Add(y.A0, y.A1) + a = api.Inner.Mul(a, b) + + b = api.Inner.Mul(x.A0, y.A0) + c := api.Inner.Mul(x.A1, y.A1) + + return E2{ + A0: api.Inner.Sub(b, api.Inner.Mul(11, c)), + A1: api.Inner.Sub(a, b, c), + } +} + +// Square e2 elt +func (api_ *API) Square(x E2) E2 { + + var a, b, c frontend.Variable + api := api_.Inner + + a = api.Mul(2, x.A0, x.A1) + + c = api.Mul(x.A0, x.A0) + b = api.Mul(x.A1, x.A1, 11) + + return E2{ + A0: api.Sub(c, b), + A1: a, + } +} + +// MulByFp multiplies an fp2 elmt by an fp elmt +func (api *API) MulByBase(e1 E2, c frontend.Variable) E2 { + return E2{ + A0: api.Inner.Mul(e1.A0, c), + A1: api.Inner.Mul(e1.A1, c), + } +} + +// MulByNonResidue multiplies an fp2 elmt by the imaginary elmt +// ext.uSquare is the square of the imaginary root +func (api *API) MulByNonResidue(e1 E2) E2 { + return E2{ + A0: api.Inner.Mul(e1.A1, -11), + A1: e1.A0, + } +} + +// Conjugate conjugation of an e2 elmt +func (api *API) Conjugate(e1 E2) E2 { + return E2{ + A0: e1.A0, + A1: api.Inner.Sub(0, e1.A1), + } +} + +// Inverse e2 elmts +func (api_ *API) Inverse(x E2) E2 { + + api := api_.Inner + + a := x.A0 // creating the buffers a, b is faster than querying &x.A0, &x.A1 in the functions call below + b := x.A1 + t0 := api.Mul(a, a) + t1 := api.Mul(b, b) + tmp := t1 + tmp = api.Mul(tmp, 11) + t0 = api.Add(t0, tmp) + t1 = api.Inverse(t0) + + return E2{ + A0: api.Mul(a, t1), + A1: api.Mul(b, t1, -1), + } +} + +// Assign a value to self (witness assignment) +func Assign(a *fext.Element) E2 { + return E2{ + A0: (fr.Element)(a.A0), + A1: (fr.Element)(a.A1), + } +} + +// AssertIsEqual constraint self to be equal to other into the given constraint system +func (api *API) AssertIsEqual(e, other E2) { + api.Inner.AssertIsEqual(e.A0, other.A0) + api.Inner.AssertIsEqual(e.A1, other.A1) +} + +// Select sets e to r1 if b=1, r2 otherwise +func (api *API) Select(b frontend.Variable, r1, r2 E2) E2 { + return E2{ + A0: api.Inner.Select(b, r1.A0, r2.A0), + A1: api.Inner.Select(b, r1.A1, r2.A1), + } +} + +// Lookup2 implements two-bit lookup. It returns: +// - r1 if b1=0 and b2=0, +// - r2 if b1=0 and b2=1, +// - r3 if b1=1 and b2=0, +// - r3 if b1=1 and b2=1. +func (api *API) Lookup2(b1, b2 frontend.Variable, r1, r2, r3, r4 E2) E2 { + return E2{ + A0: api.Inner.Lookup2(b1, b2, r1.A0, r2.A0, r3.A0, r4.A0), + A1: api.Inner.Lookup2(b1, b2, r1.A1, r2.A1, r3.A1, r4.A1), + } +} diff --git a/prover/maths/field/fext/utils.go b/prover/maths/field/fext/utils.go index f4355c403..30f5a934e 100644 --- a/prover/maths/field/fext/utils.go +++ b/prover/maths/field/fext/utils.go @@ -36,24 +36,3 @@ func GenE2() gopter.Gen { return &E2{A0: values[0].(fr.Element), A1: values[1].(fr.Element)} }) } - -// GenE3 generates an E3 elmt -func GenE3() gopter.Gen { - return gopter.CombineGens( - GenFr(), - GenFr(), - GenFr(), - ).Map(func(values []interface{}) *E3 { - return &E3{A0: values[0].(fr.Element), A1: values[1].(fr.Element), A2: values[2].(fr.Element)} - }) -} - -// E4 generates an E4 elmt -func GenE4() gopter.Gen { - return gopter.CombineGens( - GenE2(), - GenE2(), - ).Map(func(values []interface{}) *E4 { - return &E4{B0: *values[0].(*E2), B1: *values[1].(*E2)} - }) -}