diff --git a/internal/stats/latest.stats b/internal/stats/latest.stats index b5f1781ab7..ca6fe17b4f 100644 Binary files a/internal/stats/latest.stats and b/internal/stats/latest.stats differ diff --git a/std/algebra/emulated/fields_bls12381/e6.go b/std/algebra/emulated/fields_bls12381/e6.go index 815af2262a..22a3596324 100644 --- a/std/algebra/emulated/fields_bls12381/e6.go +++ b/std/algebra/emulated/fields_bls12381/e6.go @@ -1,8 +1,11 @@ package fields_bls12381 import ( + "math/big" + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/internal/frontendtype" ) type E6 struct { @@ -79,7 +82,116 @@ func (e Ext6) Sub(x, y *E6) *E6 { } } +// Mul multiplies two E6 elmts func (e Ext6) Mul(x, y *E6) *E6 { + if ft, ok := e.api.(frontendtype.FrontendTyper); ok { + switch ft.FrontendType() { + case frontendtype.R1CS: + return e.mulToom3OverKaratsuba(x, y) + case frontendtype.SCS: + return e.mulKaratsubaOverKaratsuba(x, y) + } + } + return e.mulKaratsubaOverKaratsuba(x, y) +} + +func (e Ext6) mulToom3OverKaratsuba(x, y *E6) *E6 { + // Toom-Cook-3x over Karatsuba: + // We start by computing five interpolation points – these are evaluations of + // the product x(u)y(u) with u ∈ {0, ±1, 2, ∞}: + // + // v0 = x(0)y(0) = x.A0 * y.A0 + // v1 = x(1)y(1) = (x.A0 + x.A1 + x.A2)(y.A0 + y.A1 + y.A2) + // v2 = x(−1)y(−1) = (x.A0 − x.A1 + x.A2)(y.A0 − y.A1 + y.A2) + // v3 = x(2)y(2) = (x.A0 + 2x.A1 + 4x.A2)(y.A0 + 2y.A1 + 4y.A2) + // v4 = x(∞)y(∞) = x.A2 * y.A2 + + v0 := e.Ext2.Mul(&x.B0, &y.B0) + + t1 := e.Ext2.Add(&x.B0, &x.B2) + t2 := e.Ext2.Add(&y.B0, &y.B2) + t3 := e.Ext2.Add(t2, &y.B1) + v1 := e.Ext2.Add(t1, &x.B1) + v1 = e.Ext2.Mul(v1, t3) + + t3 = e.Ext2.Sub(t2, &y.B1) + v2 := e.Ext2.Sub(t1, &x.B1) + v2 = e.Ext2.Mul(v2, t3) + + t1 = e.Ext2.MulByConstElement(&x.B1, big.NewInt(2)) + t2 = e.Ext2.MulByConstElement(&x.B2, big.NewInt(4)) + v3 := e.Ext2.Add(t1, t2) + v3 = e.Ext2.Add(v3, &x.B0) + t1 = e.Ext2.MulByConstElement(&y.B1, big.NewInt(2)) + t2 = e.Ext2.MulByConstElement(&y.B2, big.NewInt(4)) + t3 = e.Ext2.Add(t1, t2) + t3 = e.Ext2.Add(t3, &y.B0) + v3 = e.Ext2.Mul(v3, t3) + + v4 := e.Ext2.Mul(&x.B2, &y.B2) + + // Then the interpolation is performed as: + // + // a0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) + // a1 = −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 + // a2 = −v0 + (1/2)v1 + (1/2)v2 − v4 + // + // where β is the cubic non-residue. + // + // In-circuit, we compute 6*x*y as + // c0 = 6v0 + β(3v0 − 3v1 − v2 + v3 − 12v4) + // a1 = -(3v0 + 2v2 + v3) + 6(v1 + 2v4 + βv4) + // a2 = 3(v1 + v2 - 2(v0 + v4)) + // + // and then divide a0, a1 and a2 by 6 using a hint. + + a0 := e.Ext2.MulByConstElement(v0, big.NewInt(6)) + t1 = e.Ext2.Sub(v0, v1) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(3)) + t1 = e.Ext2.Sub(t1, v2) + t1 = e.Ext2.Add(t1, v3) + t2 = e.Ext2.MulByConstElement(v4, big.NewInt(12)) + t1 = e.Ext2.Sub(t1, t2) + t1 = e.Ext2.MulByNonResidue(t1) + a0 = e.Ext2.Add(a0, t1) + + a1 := e.Ext2.MulByConstElement(v0, big.NewInt(3)) + t1 = e.Ext2.MulByConstElement(v2, big.NewInt(2)) + a1 = e.Ext2.Add(a1, t1) + a1 = e.Ext2.Add(a1, v3) + t1 = e.Ext2.MulByConstElement(v4, big.NewInt(2)) + t1 = e.Ext2.Add(t1, v1) + t2 = e.Ext2.MulByNonResidue(v4) + t1 = e.Ext2.Add(t1, t2) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) + a1 = e.Ext2.Sub(t1, a1) + + a2 := e.Ext2.Add(v1, v2) + a2 = e.Ext2.MulByConstElement(a2, big.NewInt(3)) + t1 = e.Ext2.Add(v0, v4) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) + a2 = e.Ext2.Sub(a2, t1) + + res := e.divE6By6([6]*baseEl{&a0.A0, &a0.A1, &a1.A0, &a1.A1, &a2.A0, &a2.A1}) + return &E6{ + B0: E2{ + A0: *res[0], + A1: *res[1], + }, + B1: E2{ + A0: *res[2], + A1: *res[3], + }, + B2: E2{ + A0: *res[4], + A1: *res[5], + }, + } +} + +func (e Ext6) mulKaratsubaOverKaratsuba(x, y *E6) *E6 { + // Karatsuba over Karatsuba: + // Algorithm 13 from https://eprint.iacr.org/2010/354.pdf t0 := e.Ext2.Mul(&x.B0, &y.B0) t1 := e.Ext2.Mul(&x.B1, &y.B1) t2 := e.Ext2.Mul(&x.B2, &y.B2) @@ -307,6 +419,37 @@ func (e Ext6) DivUnchecked(x, y *E6) *E6 { return &div } +func (e Ext6) divE6By6(x [6]*baseEl) [6]*baseEl { + res, err := e.fp.NewHint(divE6By6Hint, 6, x[0], x[1], x[2], x[3], x[4], x[5]) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + y0 := *res[0] + y1 := *res[1] + y2 := *res[2] + y3 := *res[3] + y4 := *res[4] + y5 := *res[5] + + // xi == 6 * yi + x0 := e.fp.MulConst(&y0, big.NewInt(6)) + x1 := e.fp.MulConst(&y1, big.NewInt(6)) + x2 := e.fp.MulConst(&y2, big.NewInt(6)) + x3 := e.fp.MulConst(&y3, big.NewInt(6)) + x4 := e.fp.MulConst(&y4, big.NewInt(6)) + x5 := e.fp.MulConst(&y5, big.NewInt(6)) + e.fp.AssertIsEqual(x[0], x0) + e.fp.AssertIsEqual(x[1], x1) + e.fp.AssertIsEqual(x[2], x2) + e.fp.AssertIsEqual(x[3], x3) + e.fp.AssertIsEqual(x[4], x4) + e.fp.AssertIsEqual(x[5], x5) + + return [6]*baseEl{&y0, &y1, &y2, &y3, &y4, &y5} +} + func (e Ext6) Select(selector frontend.Variable, z1, z0 *E6) *E6 { b0 := e.Ext2.Select(selector, &z1.B0, &z0.B0) b1 := e.Ext2.Select(selector, &z1.B1, &z0.B1) diff --git a/std/algebra/emulated/fields_bls12381/e6_test.go b/std/algebra/emulated/fields_bls12381/e6_test.go index 7964833bdf..9ed08d3b51 100644 --- a/std/algebra/emulated/fields_bls12381/e6_test.go +++ b/std/algebra/emulated/fields_bls12381/e6_test.go @@ -102,6 +102,39 @@ func TestMulFp6(t *testing.T) { } +type e6MulVariant struct { + A, B, C E6 +} + +func (circuit *e6MulVariant) Define(api frontend.API) error { + e := NewExt6(api) + expected1 := e.mulKaratsubaOverKaratsuba(&circuit.A, &circuit.B) + expected2 := e.mulToom3OverKaratsuba(&circuit.A, &circuit.B) + e.AssertIsEqual(expected1, &circuit.C) + e.AssertIsEqual(expected2, &circuit.C) + return nil +} + +func TestMulFp6Variants(t *testing.T) { + + assert := test.NewAssert(t) + // witness values + var a, b, c bls12381.E6 + _, _ = a.SetRandom() + _, _ = b.SetRandom() + c.Mul(&a, &b) + + witness := e6Mul{ + A: FromE6(&a), + B: FromE6(&b), + C: FromE6(&c), + } + + err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} + type e6Square struct { A, C E6 } diff --git a/std/algebra/emulated/fields_bls12381/hints.go b/std/algebra/emulated/fields_bls12381/hints.go index c8455f40cf..fdc9700504 100644 --- a/std/algebra/emulated/fields_bls12381/hints.go +++ b/std/algebra/emulated/fields_bls12381/hints.go @@ -4,6 +4,7 @@ import ( "math/big" bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" ) @@ -22,6 +23,7 @@ func GetHints() []solver.Hint { divE6Hint, inverseE6Hint, squareTorusHint, + divE6By6Hint, // E12 divE12Hint, inverseE12Hint, @@ -149,6 +151,36 @@ func squareTorusHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) }) } +func divE6By6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var a, c bls12381.E6 + + a.B0.A0.SetBigInt(inputs[0]) + a.B0.A1.SetBigInt(inputs[1]) + a.B1.A0.SetBigInt(inputs[2]) + a.B1.A1.SetBigInt(inputs[3]) + a.B2.A0.SetBigInt(inputs[4]) + a.B2.A1.SetBigInt(inputs[5]) + + var sixInv fp.Element + sixInv.SetString("6") + sixInv.Inverse(&sixInv) + c.B0.MulByElement(&a.B0, &sixInv) + c.B1.MulByElement(&a.B1, &sixInv) + c.B2.MulByElement(&a.B2, &sixInv) + + c.B0.A0.BigInt(outputs[0]) + c.B0.A1.BigInt(outputs[1]) + c.B1.A0.BigInt(outputs[2]) + c.B1.A1.BigInt(outputs[3]) + c.B2.A0.BigInt(outputs[4]) + c.B2.A1.BigInt(outputs[5]) + + return nil + }) +} + // E12 hints func inverseE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { return emulated.UnwrapHint(nativeInputs, nativeOutputs, diff --git a/std/algebra/emulated/fields_bn254/e6.go b/std/algebra/emulated/fields_bn254/e6.go index f7bf6158e2..584043114c 100644 --- a/std/algebra/emulated/fields_bn254/e6.go +++ b/std/algebra/emulated/fields_bn254/e6.go @@ -1,8 +1,11 @@ package fields_bn254 import ( + "math/big" + "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/internal/frontendtype" ) type E6 struct { @@ -79,7 +82,116 @@ func (e Ext6) Sub(x, y *E6) *E6 { } } +// Mul multiplies two E6 elmts func (e Ext6) Mul(x, y *E6) *E6 { + if ft, ok := e.api.(frontendtype.FrontendTyper); ok { + switch ft.FrontendType() { + case frontendtype.R1CS: + return e.mulToom3OverKaratsuba(x, y) + case frontendtype.SCS: + return e.mulKaratsubaOverKaratsuba(x, y) + } + } + return e.mulKaratsubaOverKaratsuba(x, y) +} + +func (e Ext6) mulToom3OverKaratsuba(x, y *E6) *E6 { + // Toom-Cook-3x over Karatsuba: + // We start by computing five interpolation points – these are evaluations of + // the product x(u)y(u) with u ∈ {0, ±1, 2, ∞}: + // + // v0 = x(0)y(0) = x.A0 * y.A0 + // v1 = x(1)y(1) = (x.A0 + x.A1 + x.A2)(y.A0 + y.A1 + y.A2) + // v2 = x(−1)y(−1) = (x.A0 − x.A1 + x.A2)(y.A0 − y.A1 + y.A2) + // v3 = x(2)y(2) = (x.A0 + 2x.A1 + 4x.A2)(y.A0 + 2y.A1 + 4y.A2) + // v4 = x(∞)y(∞) = x.A2 * y.A2 + + v0 := e.Ext2.Mul(&x.B0, &y.B0) + + t1 := e.Ext2.Add(&x.B0, &x.B2) + t2 := e.Ext2.Add(&y.B0, &y.B2) + t3 := e.Ext2.Add(t2, &y.B1) + v1 := e.Ext2.Add(t1, &x.B1) + v1 = e.Ext2.Mul(v1, t3) + + t3 = e.Ext2.Sub(t2, &y.B1) + v2 := e.Ext2.Sub(t1, &x.B1) + v2 = e.Ext2.Mul(v2, t3) + + t1 = e.Ext2.MulByConstElement(&x.B1, big.NewInt(2)) + t2 = e.Ext2.MulByConstElement(&x.B2, big.NewInt(4)) + v3 := e.Ext2.Add(t1, t2) + v3 = e.Ext2.Add(v3, &x.B0) + t1 = e.Ext2.MulByConstElement(&y.B1, big.NewInt(2)) + t2 = e.Ext2.MulByConstElement(&y.B2, big.NewInt(4)) + t3 = e.Ext2.Add(t1, t2) + t3 = e.Ext2.Add(t3, &y.B0) + v3 = e.Ext2.Mul(v3, t3) + + v4 := e.Ext2.Mul(&x.B2, &y.B2) + + // Then the interpolation is performed as: + // + // a0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) + // a1 = −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 + // a2 = −v0 + (1/2)v1 + (1/2)v2 − v4 + // + // where β is the cubic non-residue. + // + // In-circuit, we compute 6*x*y as + // c0 = 6v0 + β(3v0 − 3v1 − v2 + v3 − 12v4) + // a1 = -(3v0 + 2v2 + v3) + 6(v1 + 2v4 + βv4) + // a2 = 3(v1 + v2 - 2(v0 + v4)) + // + // and then divide a0, a1 and a2 by 6 using a hint. + + a0 := e.Ext2.MulByConstElement(v0, big.NewInt(6)) + t1 = e.Ext2.Sub(v0, v1) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(3)) + t1 = e.Ext2.Sub(t1, v2) + t1 = e.Ext2.Add(t1, v3) + t2 = e.Ext2.MulByConstElement(v4, big.NewInt(12)) + t1 = e.Ext2.Sub(t1, t2) + t1 = e.Ext2.MulByNonResidue(t1) + a0 = e.Ext2.Add(a0, t1) + + a1 := e.Ext2.MulByConstElement(v0, big.NewInt(3)) + t1 = e.Ext2.MulByConstElement(v2, big.NewInt(2)) + a1 = e.Ext2.Add(a1, t1) + a1 = e.Ext2.Add(a1, v3) + t1 = e.Ext2.MulByConstElement(v4, big.NewInt(2)) + t1 = e.Ext2.Add(t1, v1) + t2 = e.Ext2.MulByNonResidue(v4) + t1 = e.Ext2.Add(t1, t2) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) + a1 = e.Ext2.Sub(t1, a1) + + a2 := e.Ext2.Add(v1, v2) + a2 = e.Ext2.MulByConstElement(a2, big.NewInt(3)) + t1 = e.Ext2.Add(v0, v4) + t1 = e.Ext2.MulByConstElement(t1, big.NewInt(6)) + a2 = e.Ext2.Sub(a2, t1) + + res := e.divE6By6([6]*baseEl{&a0.A0, &a0.A1, &a1.A0, &a1.A1, &a2.A0, &a2.A1}) + return &E6{ + B0: E2{ + A0: *res[0], + A1: *res[1], + }, + B1: E2{ + A0: *res[2], + A1: *res[3], + }, + B2: E2{ + A0: *res[4], + A1: *res[5], + }, + } +} + +func (e Ext6) mulKaratsubaOverKaratsuba(x, y *E6) *E6 { + // Karatsuba over Karatsuba: + // Algorithm 13 from https://eprint.iacr.org/2010/354.pdf t0 := e.Ext2.Mul(&x.B0, &y.B0) t1 := e.Ext2.Mul(&x.B1, &y.B1) t2 := e.Ext2.Mul(&x.B2, &y.B2) @@ -148,6 +260,17 @@ func (e Ext6) Square(x *E6) *E6 { } } +func (e Ext6) MulByConstE2(x *E6, y *E2) *E6 { + z0 := e.Ext2.Mul(&x.B0, y) + z1 := e.Ext2.Mul(&x.B1, y) + z2 := e.Ext2.Mul(&x.B2, y) + return &E6{ + B0: *z0, + B1: *z1, + B2: *z2, + } +} + func (e Ext6) MulByE2(x *E6, y *E2) *E6 { z0 := e.Ext2.Mul(&x.B0, y) z1 := e.Ext2.Mul(&x.B1, y) @@ -317,6 +440,37 @@ func (e Ext6) DivUnchecked(x, y *E6) *E6 { return &div } +func (e Ext6) divE6By6(x [6]*baseEl) [6]*baseEl { + res, err := e.fp.NewHint(divE6By6Hint, 6, x[0], x[1], x[2], x[3], x[4], x[5]) + if err != nil { + // err is non-nil only for invalid number of inputs + panic(err) + } + + y0 := *res[0] + y1 := *res[1] + y2 := *res[2] + y3 := *res[3] + y4 := *res[4] + y5 := *res[5] + + // xi == 6 * yi + x0 := e.fp.MulConst(&y0, big.NewInt(6)) + x1 := e.fp.MulConst(&y1, big.NewInt(6)) + x2 := e.fp.MulConst(&y2, big.NewInt(6)) + x3 := e.fp.MulConst(&y3, big.NewInt(6)) + x4 := e.fp.MulConst(&y4, big.NewInt(6)) + x5 := e.fp.MulConst(&y5, big.NewInt(6)) + e.fp.AssertIsEqual(x[0], x0) + e.fp.AssertIsEqual(x[1], x1) + e.fp.AssertIsEqual(x[2], x2) + e.fp.AssertIsEqual(x[3], x3) + e.fp.AssertIsEqual(x[4], x4) + e.fp.AssertIsEqual(x[5], x5) + + return [6]*baseEl{&y0, &y1, &y2, &y3, &y4, &y5} +} + func (e Ext6) Select(selector frontend.Variable, z1, z0 *E6) *E6 { b0 := e.Ext2.Select(selector, &z1.B0, &z0.B0) b1 := e.Ext2.Select(selector, &z1.B1, &z0.B1) diff --git a/std/algebra/emulated/fields_bn254/e6_test.go b/std/algebra/emulated/fields_bn254/e6_test.go index 9b8dfdac74..4fbfcdea8b 100644 --- a/std/algebra/emulated/fields_bn254/e6_test.go +++ b/std/algebra/emulated/fields_bn254/e6_test.go @@ -102,6 +102,39 @@ func TestMulFp6(t *testing.T) { } +type e6MulVariant struct { + A, B, C E6 +} + +func (circuit *e6MulVariant) Define(api frontend.API) error { + e := NewExt6(api) + expected1 := e.mulKaratsubaOverKaratsuba(&circuit.A, &circuit.B) + expected2 := e.mulToom3OverKaratsuba(&circuit.A, &circuit.B) + e.AssertIsEqual(expected1, &circuit.C) + e.AssertIsEqual(expected2, &circuit.C) + return nil +} + +func TestMulFp6Variants(t *testing.T) { + + assert := test.NewAssert(t) + // witness values + var a, b, c bn254.E6 + _, _ = a.SetRandom() + _, _ = b.SetRandom() + c.Mul(&a, &b) + + witness := e6Mul{ + A: FromE6(&a), + B: FromE6(&b), + C: FromE6(&c), + } + + err := test.IsSolved(&e6Mul{}, &witness, ecc.BN254.ScalarField()) + assert.NoError(err) + +} + type e6Square struct { A, C E6 } diff --git a/std/algebra/emulated/fields_bn254/hints.go b/std/algebra/emulated/fields_bn254/hints.go index b08d6ed977..e9409af18a 100644 --- a/std/algebra/emulated/fields_bn254/hints.go +++ b/std/algebra/emulated/fields_bn254/hints.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark/constraint/solver" "github.com/consensys/gnark/std/math/emulated" ) @@ -22,6 +23,7 @@ func GetHints() []solver.Hint { divE6Hint, inverseE6Hint, squareTorusHint, + divE6By6Hint, // E12 divE12Hint, inverseE12Hint, @@ -149,6 +151,36 @@ func squareTorusHint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) }) } +func divE6By6Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { + return emulated.UnwrapHint(nativeInputs, nativeOutputs, + func(mod *big.Int, inputs, outputs []*big.Int) error { + var a, c bn254.E6 + + a.B0.A0.SetBigInt(inputs[0]) + a.B0.A1.SetBigInt(inputs[1]) + a.B1.A0.SetBigInt(inputs[2]) + a.B1.A1.SetBigInt(inputs[3]) + a.B2.A0.SetBigInt(inputs[4]) + a.B2.A1.SetBigInt(inputs[5]) + + var sixInv fp.Element + sixInv.SetString("6") + sixInv.Inverse(&sixInv) + c.B0.MulByElement(&a.B0, &sixInv) + c.B1.MulByElement(&a.B1, &sixInv) + c.B2.MulByElement(&a.B2, &sixInv) + + c.B0.A0.BigInt(outputs[0]) + c.B0.A1.BigInt(outputs[1]) + c.B1.A0.BigInt(outputs[2]) + c.B1.A1.BigInt(outputs[3]) + c.B2.A0.BigInt(outputs[4]) + c.B2.A1.BigInt(outputs[5]) + + return nil + }) +} + // E12 hints func inverseE12Hint(nativeMod *big.Int, nativeInputs, nativeOutputs []*big.Int) error { return emulated.UnwrapHint(nativeInputs, nativeOutputs, diff --git a/std/algebra/native/fields_bls12377/e12_pairing.go b/std/algebra/native/fields_bls12377/e12_pairing.go index a98616c84d..a4895b425d 100644 --- a/std/algebra/native/fields_bls12377/e12_pairing.go +++ b/std/algebra/native/fields_bls12377/e12_pairing.go @@ -7,6 +7,7 @@ func (e *E12) nSquareKarabina2345(api frontend.API, n int) { for i := 0; i < n; i++ { e.CyclotomicSquareKarabina2345(api, *e) } + e.DecompressKarabina2345(api, *e) } // nSquareKarabina12345 repeated compressed cyclotmic square @@ -14,6 +15,7 @@ func (e *E12) nSquareKarabina12345(api frontend.API, n int) { for i := 0; i < n; i++ { e.CyclotomicSquareKarabina12345(api, *e) } + e.DecompressKarabina12345(api, *e) } // Square034 squares a sparse element in Fp12 @@ -129,19 +131,15 @@ func (e *E12) ExpX0(api frontend.API, e1 E12) *E12 { res := e1 res.nSquareKarabina2345(api, 5) - res.DecompressKarabina2345(api, res) res.Mul(api, res, e1) x33 := res res.nSquareKarabina2345(api, 7) - res.DecompressKarabina2345(api, res) res.Mul(api, res, x33) res.nSquareKarabina2345(api, 4) - res.DecompressKarabina2345(api, res) res.Mul(api, res, e1) res.CyclotomicSquare(api, res) res.Mul(api, res, e1) res.nSquareKarabina2345(api, 46) - res.DecompressKarabina2345(api, res) res.Mul(api, res, e1) *e = res @@ -157,7 +155,6 @@ func (e *E12) ExpX0Minus1Square(api frontend.API, e1 E12) *E12 { res = e1 res.nSquareKarabina12345(api, 3) - res.DecompressKarabina12345(api, res) t0.CyclotomicSquare(api, res) t2.Mul(api, e1, t0) res.Mul(api, res, t2) @@ -166,20 +163,15 @@ func (e *E12) ExpX0Minus1Square(api frontend.API, e1 E12) *E12 { t1.Mul(api, t2, t1) t3 = t1 t3.nSquareKarabina2345(api, 7) - t3.DecompressKarabina2345(api, t3) t2.Mul(api, t2, t3) t2.nSquareKarabina2345(api, 11) - t2.DecompressKarabina2345(api, t2) t1.Mul(api, t1, t2) t0.Mul(api, t0, t1) t0.nSquareKarabina2345(api, 7) - t0.DecompressKarabina2345(api, t0) res.Mul(api, res, t0) res.nSquareKarabina12345(api, 3) - res.DecompressKarabina12345(api, res) - res.Mul(api, e1, res) - res.nSquareKarabina2345(api, 92) - e.DecompressKarabina2345(api, res) + e.Mul(api, e1, res) + e.nSquareKarabina2345(api, 92) return e diff --git a/std/algebra/native/fields_bls12377/e6.go b/std/algebra/native/fields_bls12377/e6.go index 05c6b194d4..b9af8c4f41 100644 --- a/std/algebra/native/fields_bls12377/e6.go +++ b/std/algebra/native/fields_bls12377/e6.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -17,9 +17,11 @@ limitations under the License. package fields_bls12377 import ( - bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" + "math/big" + bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/internal/frontendtype" ) // E6 element in a quadratic extension @@ -97,10 +99,21 @@ func (e *E6) Neg(api frontend.API, e1 E6) *E6 { return e } -// Mul creates a fp6elmt from fp elmts -// icube is the imaginary elmt to the cube +// Mul multiplies two E6 elmts func (e *E6) Mul(api frontend.API, e1, e2 E6) *E6 { + if ft, ok := api.(frontendtype.FrontendTyper); ok { + switch ft.FrontendType() { + case frontendtype.R1CS: + return e.mulToom3OverKaratsuba(api, e1, e2) + case frontendtype.SCS: + return e.mulKaratsubaOverKaratsuba(api, e1, e2) + } + } + return e.mulKaratsubaOverKaratsuba(api, e1, e2) +} +func (e *E6) mulKaratsubaOverKaratsuba(api frontend.API, e1, e2 E6) *E6 { + // Karatsuba over Karatsuba: // Algorithm 13 from https://eprint.iacr.org/2010/354.pdf var t0, t1, t2, c0, c1, c2, tmp E2 t0.Mul(api, e1.B0, e2.B0) @@ -137,6 +150,94 @@ func (e *E6) Mul(api frontend.API, e1, e2 E6) *E6 { return e } +func (e *E6) mulToom3OverKaratsuba(api frontend.API, e1, e2 E6) *E6 { + // Toom-Cook-3x over Karatsuba: + // We start by computing five interpolation points – these are evaluations of + // the product x(u)y(u) with u ∈ {0, ±1, 2, ∞}: + // + // v0 = x(0)y(0) = x.A0 * y.A0 + // v1 = x(1)y(1) = (x.A0 + x.A1 + x.A2)(y.A0 + y.A1 + y.A2) + // v2 = x(−1)y(−1) = (x.A0 − x.A1 + x.A2)(y.A0 − y.A1 + y.A2) + // v3 = x(2)y(2) = (x.A0 + 2x.A1 + 4x.A2)(y.A0 + 2y.A1 + 4y.A2) + // v4 = x(∞)y(∞) = x.A2 * y.A2 + var v0, v1, v2, v3, v4, t1, t2, t3 E2 + + v0.Mul(api, e1.B0, e2.B0) + + t1.Add(api, e1.B0, e1.B2) + t2.Add(api, e2.B0, e2.B2) + t3.Add(api, t2, e2.B1) + v1.Add(api, t1, e1.B1) + v1.Mul(api, v1, t3) + + t3.Sub(api, t2, e2.B1) + v2.Sub(api, t1, e1.B1) + v2.Mul(api, v2, t3) + + t1.MulByFp(api, e1.B1, big.NewInt(2)) + t2.MulByFp(api, e1.B2, big.NewInt(4)) + v3.Add(api, t1, t2) + v3.Add(api, v3, e1.B0) + t1.MulByFp(api, e2.B1, big.NewInt(2)) + t2.MulByFp(api, e2.B2, big.NewInt(4)) + t3.Add(api, t1, t2) + t3.Add(api, t3, e2.B0) + v3.Mul(api, v3, t3) + + v4.Mul(api, e1.B2, e2.B2) + + // Then the interpolation is performed as: + // + // a0 = v0 + β((1/2)v0 − (1/2)v1 − (1/6)v2 + (1/6)v3 − 2v4) + // a1 = −(1/2)v0 + v1 − (1/3)v2 − (1/6)v3 + 2v4 + βv4 + // a2 = −v0 + (1/2)v1 + (1/2)v2 − v4 + // + // where β is the cubic non-residue. + // + // In-circuit, we compute 6*x*y as + // c0 = 6v0 + β(3v0 − 3v1 − v2 + v3 − 12v4) + // a1 = -(3v0 + 2v2 + v3) + 6(v1 + 2v4 + βv4) + // a2 = 3(v1 + v2 - 2(v0 + v4)) + // + // and then divide a0, a1 and a2 by 6 using a hint. + var a0, a1, a2 E2 + a0.MulByFp(api, v0, big.NewInt(6)) + t1.Sub(api, v0, v1) + t1.MulByFp(api, t1, big.NewInt(3)) + t1.Sub(api, t1, v2) + t1.Add(api, t1, v3) + t2.MulByFp(api, v4, big.NewInt(12)) + t1.Sub(api, t1, t2) + t1.MulByNonResidue(api, t1) + a0.Add(api, a0, t1) + + a1.MulByFp(api, v0, big.NewInt(3)) + t1.MulByFp(api, v2, big.NewInt(2)) + a1.Add(api, a1, t1) + a1.Add(api, a1, v3) + t1.MulByFp(api, v4, big.NewInt(2)) + t1.Add(api, t1, v1) + t2.MulByNonResidue(api, v4) + t1.Add(api, t1, t2) + t1.MulByFp(api, t1, big.NewInt(6)) + a1.Sub(api, t1, a1) + + a2.Add(api, v1, v2) + a2.MulByFp(api, a2, big.NewInt(3)) + t1.Add(api, v0, v4) + t1.MulByFp(api, t1, big.NewInt(6)) + a2.Sub(api, a2, t1) + + e.B0.A0 = api.Div(a0.A0, 6) + e.B0.A1 = api.Div(a0.A1, 6) + e.B1.A0 = api.Div(a1.A0, 6) + e.B1.A1 = api.Div(a1.A1, 6) + e.B2.A0 = api.Div(a2.A0, 6) + e.B2.A1 = api.Div(a2.A1, 6) + + return e +} + func (e *E6) Mul0By01(api frontend.API, a0, b0, b1 E2) *E6 { var t0, c1 E2 diff --git a/std/algebra/native/fields_bls12377/e6_test.go b/std/algebra/native/fields_bls12377/e6_test.go index 60e849605e..9024377490 100644 --- a/std/algebra/native/fields_bls12377/e6_test.go +++ b/std/algebra/native/fields_bls12377/e6_test.go @@ -122,6 +122,42 @@ func TestMulFp6(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } +type fp6MulVariants struct { + A, B E6 + C E6 `gnark:",public"` +} + +func (circuit *fp6MulVariants) Define(api frontend.API) error { + expected1 := E6{} + expected2 := E6{} + + expected1.mulKaratsubaOverKaratsuba(api, circuit.A, circuit.B) + expected2.mulToom3OverKaratsuba(api, circuit.A, circuit.B) + + expected1.AssertIsEqual(api, circuit.C) + expected2.AssertIsEqual(api, circuit.C) + return nil +} + +func TestMulVariantsFp6(t *testing.T) { + + var circuit, witness fp6MulVariants + + // witness values + var a, b, c bls12377.E6 + _, _ = a.SetRandom() + _, _ = b.SetRandom() + c.Mul(&a, &b) + + witness.A.Assign(&a) + witness.B.Assign(&b) + witness.C.Assign(&c) + + // cs values + assert := test.NewAssert(t) + assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) +} + type fp6MulBy01 struct { A E6 C0, C1 E2