diff --git a/ecc/bls12-377/internal/fptower/e12.go b/ecc/bls12-377/internal/fptower/e12.go index d04c2c2242..723902e2bf 100644 --- a/ecc/bls12-377/internal/fptower/e12.go +++ b/ecc/bls12-377/internal/fptower/e12.go @@ -19,10 +19,19 @@ package fptower import ( "encoding/binary" "errors" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E12 is a degree two finite field extension of fp6 type E12 struct { C0, C1 E6 @@ -406,22 +415,171 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q¹²) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E12) CyclotomicExp(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var res, xInv E12 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q¹²) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E12) ExpGLV(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var table [15]E12 + var res E12 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } diff --git a/ecc/bls12-377/internal/fptower/e12_test.go b/ecc/bls12-377/internal/fptower/e12_test.go index 036a4bdb93..6fe75b79f2 100644 --- a/ecc/bls12-377/internal/fptower/e12_test.go +++ b/ecc/bls12-377/internal/fptower/e12_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" @@ -195,6 +196,7 @@ func TestE12Ops(t *testing.T) { genA := GenE12() genB := GenE12() + genExp := GenFp() properties.Property("[BLS12-377] sub & add should leave an element invariant", prop.ForAll( func(a, b *E12) bool { @@ -375,12 +377,35 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-377] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E12, e fp.Element) bool { + var b, c, d E12 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BLS12-377] Frobenius of x in E12 should be equal to x^q", prop.ForAll( func(a *E12) bool { var b, c E12 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, @@ -391,7 +416,7 @@ func TestE12Ops(t *testing.T) { var b, c E12 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls12-377/internal/fptower/e2.go b/ecc/bls12-377/internal/fptower/e2.go index ee79f4c9d9..fc300952ca 100644 --- a/ecc/bls12-377/internal/fptower/e2.go +++ b/ecc/bls12-377/internal/fptower/e2.go @@ -170,10 +170,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bls12-377/internal/fptower/parameters.go b/ecc/bls12-377/internal/fptower/parameters.go new file mode 100644 index 0000000000..6f2b1d6c7e --- /dev/null +++ b/ecc/bls12-377/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("9586122913090633729", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bls12-377/pairing_test.go b/ecc/bls12-377/pairing_test.go index 55e729f072..c64efebada 100644 --- a/ecc/bls12-377/pairing_test.go +++ b/ecc/bls12-377/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -44,6 +45,7 @@ func TestPairing(t *testing.T) { genA := GenE12() genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BLS12-377] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -63,6 +65,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BLS12-377] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BLS12-377] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -74,7 +100,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -101,9 +127,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -352,3 +378,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bls12-378/internal/fptower/e12.go b/ecc/bls12-378/internal/fptower/e12.go index bf400bfb19..0169ee5054 100644 --- a/ecc/bls12-378/internal/fptower/e12.go +++ b/ecc/bls12-378/internal/fptower/e12.go @@ -19,10 +19,19 @@ package fptower import ( "encoding/binary" "errors" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-378/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-378/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E12 is a degree two finite field extension of fp6 type E12 struct { C0, C1 E6 @@ -406,22 +415,171 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q¹²) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E12) CyclotomicExp(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var res, xInv E12 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q¹²) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E12) ExpGLV(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var table [15]E12 + var res E12 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } diff --git a/ecc/bls12-378/internal/fptower/e12_test.go b/ecc/bls12-378/internal/fptower/e12_test.go index c854e3515d..2ce5f01057 100644 --- a/ecc/bls12-378/internal/fptower/e12_test.go +++ b/ecc/bls12-378/internal/fptower/e12_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bls12-378/fp" @@ -195,6 +196,7 @@ func TestE12Ops(t *testing.T) { genA := GenE12() genB := GenE12() + genExp := GenFp() properties.Property("[BLS12-378] sub & add should leave an element invariant", prop.ForAll( func(a, b *E12) bool { @@ -375,12 +377,35 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-378] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E12, e fp.Element) bool { + var b, c, d E12 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BLS12-378] Frobenius of x in E12 should be equal to x^q", prop.ForAll( func(a *E12) bool { var b, c E12 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, @@ -391,7 +416,7 @@ func TestE12Ops(t *testing.T) { var b, c E12 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls12-378/internal/fptower/e2.go b/ecc/bls12-378/internal/fptower/e2.go index 4ca5593160..55fd82e0b5 100644 --- a/ecc/bls12-378/internal/fptower/e2.go +++ b/ecc/bls12-378/internal/fptower/e2.go @@ -170,10 +170,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bls12-378/internal/fptower/parameters.go b/ecc/bls12-378/internal/fptower/parameters.go new file mode 100644 index 0000000000..7d5ea1a4c3 --- /dev/null +++ b/ecc/bls12-378/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls12-378/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("11045256207009841153", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bls12-378/pairing_test.go b/ecc/bls12-378/pairing_test.go index d3f6377ca1..790d64d886 100644 --- a/ecc/bls12-378/pairing_test.go +++ b/ecc/bls12-378/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls12-378/fp" "github.com/consensys/gnark-crypto/ecc/bls12-378/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -44,6 +45,7 @@ func TestPairing(t *testing.T) { genA := GenE12() genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BLS12-378] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -63,6 +65,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BLS12-378] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BLS12-378] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -74,7 +100,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -101,9 +127,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -352,3 +378,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bls12-381/internal/fptower/e12.go b/ecc/bls12-381/internal/fptower/e12.go index 7be45edacf..0eaa9f3df4 100644 --- a/ecc/bls12-381/internal/fptower/e12.go +++ b/ecc/bls12-381/internal/fptower/e12.go @@ -19,10 +19,19 @@ package fptower import ( "encoding/binary" "errors" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E12 is a degree two finite field extension of fp6 type E12 struct { C0, C1 E6 @@ -406,22 +415,171 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q¹²) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E12) CyclotomicExp(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var res, xInv E12 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q¹²) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E12) ExpGLV(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var table [15]E12 + var res E12 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } diff --git a/ecc/bls12-381/internal/fptower/e12_test.go b/ecc/bls12-381/internal/fptower/e12_test.go index da63222544..0d5f9cd4ae 100644 --- a/ecc/bls12-381/internal/fptower/e12_test.go +++ b/ecc/bls12-381/internal/fptower/e12_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" @@ -195,6 +196,7 @@ func TestE12Ops(t *testing.T) { genA := GenE12() genB := GenE12() + genExp := GenFp() properties.Property("[BLS12-381] sub & add should leave an element invariant", prop.ForAll( func(a, b *E12) bool { @@ -375,12 +377,35 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-381] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E12, e fp.Element) bool { + var b, c, d E12 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BLS12-381] Frobenius of x in E12 should be equal to x^q", prop.ForAll( func(a *E12) bool { var b, c E12 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, @@ -391,7 +416,7 @@ func TestE12Ops(t *testing.T) { var b, c E12 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls12-381/internal/fptower/e2.go b/ecc/bls12-381/internal/fptower/e2.go index 20d6479a4b..6dcb1aca0e 100644 --- a/ecc/bls12-381/internal/fptower/e2.go +++ b/ecc/bls12-381/internal/fptower/e2.go @@ -170,10 +170,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bls12-381/internal/fptower/parameters.go b/ecc/bls12-381/internal/fptower/parameters.go new file mode 100644 index 0000000000..9f97e11751 --- /dev/null +++ b/ecc/bls12-381/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("-15132376222941642752", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bls12-381/pairing_test.go b/ecc/bls12-381/pairing_test.go index 15528de76d..3262379ef8 100644 --- a/ecc/bls12-381/pairing_test.go +++ b/ecc/bls12-381/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -44,6 +45,7 @@ func TestPairing(t *testing.T) { genA := GenE12() genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BLS12-381] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -63,6 +65,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BLS12-381] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BLS12-381] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -74,7 +100,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -101,9 +127,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -352,3 +378,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bls24-315/internal/fptower/e12.go b/ecc/bls24-315/internal/fptower/e12.go index 535122131d..faa9d387c8 100644 --- a/ecc/bls24-315/internal/fptower/e12.go +++ b/ecc/bls24-315/internal/fptower/e12.go @@ -240,23 +240,49 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } z.Set(&res) + return z } diff --git a/ecc/bls24-315/internal/fptower/e12_test.go b/ecc/bls24-315/internal/fptower/e12_test.go index ee431c5bc0..c4cec4957f 100644 --- a/ecc/bls24-315/internal/fptower/e12_test.go +++ b/ecc/bls24-315/internal/fptower/e12_test.go @@ -249,6 +249,6 @@ func BenchmarkE12ExpBySeed(b *testing.B) { _, _ = a.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { - a.Exp(&a, seed).Conjugate(&a) + a.Exp(a, &seed).Conjugate(&a) } } diff --git a/ecc/bls24-315/internal/fptower/e2.go b/ecc/bls24-315/internal/fptower/e2.go index e026fee0e0..de62535878 100644 --- a/ecc/bls24-315/internal/fptower/e2.go +++ b/ecc/bls24-315/internal/fptower/e2.go @@ -163,10 +163,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bls24-315/internal/fptower/e24.go b/ecc/bls24-315/internal/fptower/e24.go index 9792420ca6..043b85e0da 100644 --- a/ecc/bls24-315/internal/fptower/e24.go +++ b/ecc/bls24-315/internal/fptower/e24.go @@ -18,9 +18,18 @@ package fptower import ( "errors" + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E24 is a degree two finite field extension of fp6 type E24 struct { D0, D1 E12 @@ -144,25 +153,25 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { var t [7]E4 - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.D0.C1) - // t1 = g5^2 + // t1 = g5² t[1].Square(&x.D1.C2) // t5 = g1 + g5 t[5].Add(&x.D0.C1, &x.D1.C2) - // t2 = (g1 + g5)^2 + // t2 = (g1 + g5)² t[2].Square(&t[5]) - // t3 = g1^2 + g5^2 + // t3 = g1² + g5² t[3].Add(&t[0], &t[1]) // t5 = 2 * g1 * g5 t[5].Sub(&t[2], &t[3]) // t6 = g3 + g2 t[6].Add(&x.D1.C0, &x.D0.C2) - // t3 = (g3 + g2)^2 + // t3 = (g3 + g2)² t[3].Square(&t[6]) - // t2 = g3^2 + // t2 = g3² t[2].Square(&x.D1.C0) // t6 = 2 * nr * g1 * g5 @@ -173,33 +182,33 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { // z3 = 6 * nr * g1 * g5 + 2 * g3 z.D1.C0.Add(&t[5], &t[6]) - // t4 = nr * g5^2 + // t4 = nr * g5² t[4].MulByNonResidue(&t[1]) - // t5 = nr * g5^2 + g1^2 + // t5 = nr * g5² + g1² t[5].Add(&t[0], &t[4]) - // t6 = nr * g5^2 + g1^2 - g2 + // t6 = nr * g5² + g1² - g2 t[6].Sub(&t[5], &x.D0.C2) - // t1 = g2^2 + // t1 = g2² t[1].Square(&x.D0.C2) - // t6 = 2 * nr * g5^2 + 2 * g1^2 - 2*g2 + // t6 = 2 * nr * g5² + 2 * g1² - 2*g2 t[6].Double(&t[6]) - // z2 = 3 * nr * g5^2 + 3 * g1^2 - 2*g2 + // z2 = 3 * nr * g5² + 3 * g1² - 2*g2 z.D0.C2.Add(&t[6], &t[5]) - // t4 = nr * g2^2 + // t4 = nr * g2² t[4].MulByNonResidue(&t[1]) - // t5 = g3^2 + nr * g2^2 + // t5 = g3² + nr * g2² t[5].Add(&t[2], &t[4]) - // t6 = g3^2 + nr * g2^2 - g1 + // t6 = g3² + nr * g2² - g1 t[6].Sub(&t[5], &x.D0.C1) - // t6 = 2 * g3^2 + 2 * nr * g2^2 - 2 * g1 + // t6 = 2 * g3² + 2 * nr * g2² - 2 * g1 t[6].Double(&t[6]) - // z1 = 3 * g3^2 + 3 * nr * g2^2 - 2 * g1 + // z1 = 3 * g3² + 3 * nr * g2² - 2 * g1 z.D0.C1.Add(&t[6], &t[5]) - // t0 = g2^2 + g3^2 + // t0 = g2² + g3² t[0].Add(&t[2], &t[1]) // t5 = 2 * g3 * g2 t[5].Sub(&t[3], &t[0]) @@ -220,13 +229,13 @@ func (z *E24) DecompressKarabina(x *E24) *E24 { var one E4 one.SetOne() - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.D0.C1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t[1].Sub(&t[0], &x.D0.C2). Double(&t[1]). Add(&t[1], &t[0]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t[2].Square(&x.D1.C2) t[0].MulByNonResidue(&t[2]). Add(&t[0], &t[1]) @@ -239,14 +248,14 @@ func (z *E24) DecompressKarabina(x *E24) *E24 { // t1 = g2 * g1 t[1].Mul(&x.D0.C2, &x.D0.C1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t[2].Square(&x.D1.C1). Sub(&t[2], &t[1]). Double(&t[2]). Sub(&t[2], &t[1]) // t1 = g3 * g5 t[1].Mul(&x.D1.C0, &x.D1.C2) - // c_0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // c₀ = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t[2].Add(&t[2], &t[1]) z.D0.C0.MulByNonResidue(&t[2]). Add(&z.D0.C0, &one) @@ -275,13 +284,13 @@ func BatchDecompressKarabina(x []E24) []E24 { one.SetOne() for i := 0; i < n; i++ { - // t0 = g1^2 + // t0 = g1² t0[i].Square(&x[i].D0.C1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t1[i].Sub(&t0[i], &x[i].D0.C2). Double(&t1[i]). Add(&t1[i], &t0[i]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t2[i].Square(&x[i].D1.C2) t0[i].MulByNonResidue(&t2[i]). Add(&t0[i], &t1[i]) @@ -298,7 +307,7 @@ func BatchDecompressKarabina(x []E24) []E24 { // t1 = g2 * g1 t1[i].Mul(&x[i].D0.C2, &x[i].D0.C1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t2[i].Square(&x[i].D1.C1) t2[i].Sub(&t2[i], &t1[i]) t2[i].Double(&t2[i]) @@ -306,7 +315,7 @@ func BatchDecompressKarabina(x []E24) []E24 { // t1 = g3 * g5 t1[i].Mul(&x[i].D1.C0, &x[i].D1.C2) - // z0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // z0 = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t2[i].Add(&t2[i], &t1[i]) x[i].D0.C0.MulByNonResidue(&t2[i]). Add(&x[i].D0.C0, &one) @@ -319,10 +328,10 @@ func BatchDecompressKarabina(x []E24) []E24 { // https://eprint.iacr.org/2009/565.pdf, 3.2 func (z *E24) CyclotomicSquare(x *E24) *E24 { - // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E4^6 - // cyclosquare(x)=(3*x4^2*u + 3*x0^2 - 2*x0, - // 3*x2^2*u + 3*x3^2 - 2*x1, - // 3*x5^2*u + 3*x1^2 - 2*x2, + // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E4⁶ + // cyclosquare(x)=(3*x4²*u + 3*x0² - 2*x0, + // 3*x2²*u + 3*x3² - 2*x1, + // 3*x5²*u + 3*x1² - 2*x2, // 6*x1*x5*u + 2*x3, // 6*x0*x4 + 2*x4, // 6*x2*x3 + 2*x5) @@ -339,9 +348,9 @@ func (z *E24) CyclotomicSquare(x *E24) *E24 { t[5].Square(&x.D0.C1) t[8].Add(&x.D1.C2, &x.D0.C1).Square(&t[8]).Sub(&t[8], &t[4]).Sub(&t[8], &t[5]).MulByNonResidue(&t[8]) // 2*x5*x1*u - t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4^2*u + x0^2 - t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2^2*u + x3^2 - t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5^2*u + x1^2 + t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4²*u + x0² + t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2²*u + x3² + t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5²*u + x1² z.D0.C0.Sub(&t[0], &x.D0.C0).Double(&z.D0.C0).Add(&z.D0.C0, &t[0]) z.D0.C1.Sub(&t[2], &x.D0.C1).Double(&z.D0.C1).Add(&z.D0.C1, &t[2]) @@ -404,22 +413,171 @@ func BatchInvertE24(a []E24) []E24 { return res } -// Exp sets z=x**e and returns it -func (z *E24) Exp(x *E24, e big.Int) *E24 { +// Exp sets z=xᵏ (mod q²⁴) and returns it +// uses 2-bits windowed method +func (z *E24) Exp(x E24, k *big.Int) *E24 { + 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) + } + var res E24 + var ops [3]E24 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q²⁴) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E24) CyclotomicExp(x E24, k *big.Int) *E24 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q²⁴) == (x⁻¹)ᵏ (mod q²⁴) + x.Conjugate(&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) + } + + var res, xInv E24 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q²⁴) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E24) ExpGLV(x E24, k *big.Int) *E24 { + 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.Conjugate(&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) + } + + var table [15]E24 + var res E24 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1)/2 + 1; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } @@ -635,10 +793,10 @@ func (z *E24) IsInSubGroup() bool { // CompressTorus GT/E24 element to half its size // z must be in the cyclotomic subgroup -// i.e. z^(p^4-p^2+1)=1 +// i.e. z^(p⁴-p²+1)=1 // e.g. GT // "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG -// z.C1 == 0 only when z \in {-1,1} +// z.C1 == 0 only when z ∈ {-1,1} func (z *E24) CompressTorus() (E12, error) { if z.D1.IsZero() { diff --git a/ecc/bls24-315/internal/fptower/e24_test.go b/ecc/bls24-315/internal/fptower/e24_test.go index 0b0dbe8884..ff70344d14 100644 --- a/ecc/bls24-315/internal/fptower/e24_test.go +++ b/ecc/bls24-315/internal/fptower/e24_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bls24-315/fp" @@ -192,6 +193,7 @@ func TestE24Ops(t *testing.T) { genA := GenE24() genB := GenE24() + genExp := GenFp() properties.Property("[BLS24-315] sub & add should leave an element invariant", prop.ForAll( func(a, b *E24) bool { @@ -406,13 +408,36 @@ func TestE24Ops(t *testing.T) { genA, )) + properties.Property("[BLS24-315] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E24, e fp.Element) bool { + var b, c, d E24 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(24) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BLS24-315] Frobenius of x in E24 should be equal to x^q", prop.ForAll( func(a *E24) bool { var b, c E24 q := fp.Modulus() b.Frobenius(a) c.Set(a) - c.Exp(&c, *q) + c.Exp(c, q) return c.Equal(&b) }, genA, @@ -423,7 +448,7 @@ func TestE24Ops(t *testing.T) { var b, c E24 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, @@ -434,7 +459,7 @@ func TestE24Ops(t *testing.T) { var b, c E24 q := fp.Modulus() b.FrobeniusQuad(a) - c.Exp(a, *q).Exp(&c, *q).Exp(&c, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q).Exp(c, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls24-315/internal/fptower/e4.go b/ecc/bls24-315/internal/fptower/e4.go index 830f988691..34fe0659c4 100644 --- a/ecc/bls24-315/internal/fptower/e4.go +++ b/ecc/bls24-315/internal/fptower/e4.go @@ -215,23 +215,37 @@ func (z *E4) Inverse(x *E4) *E4 { return z } -// Exp sets z=x**e and returns it -func (z *E4) Exp(x *E4, e big.Int) *E4 { - var res E4 - res.SetOne() +// 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 := range b { + for i := 0; i < len(b); i++ { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + for j := 0; j < 8; j++ { + z.Square(z) + if (w & (0b10000000 >> j)) != 0 { + z.Mul(z, &x) } - mask = mask >> 1 } } - z.Set(&res) + return z } @@ -282,13 +296,13 @@ func (z *E4) Sqrt(x *E4) *E4 { var exp, one big.Int one.SetUint64(1) exp.Mul(q, q).Sub(&exp, &one).Rsh(&exp, 1) - d.Exp(&c, exp) + 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.Exp(*x, &exp) b.norm(&_b) o.SetOne() if _b.Equal(&o) { diff --git a/ecc/bls24-315/internal/fptower/e4_test.go b/ecc/bls24-315/internal/fptower/e4_test.go index d84267b2c2..a3a52e8f34 100644 --- a/ecc/bls24-315/internal/fptower/e4_test.go +++ b/ecc/bls24-315/internal/fptower/e4_test.go @@ -259,7 +259,7 @@ func TestE4Ops(t *testing.T) { var b, c E4 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls24-315/internal/fptower/parameters.go b/ecc/bls24-315/internal/fptower/parameters.go new file mode 100644 index 0000000000..71ac9072cd --- /dev/null +++ b/ecc/bls24-315/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("-3218079743", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bls24-315/pairing_test.go b/ecc/bls24-315/pairing_test.go index 38448b0a98..44a645790f 100644 --- a/ecc/bls24-315/pairing_test.go +++ b/ecc/bls24-315/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls24-315/fp" "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -45,6 +46,7 @@ func TestPairing(t *testing.T) { genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BLS24-315] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -64,6 +66,31 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BLS24-315] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(24) + + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BLS24-315] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -75,7 +102,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -102,9 +129,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -353,3 +380,40 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(24) + + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bls24-317/internal/fptower/e12.go b/ecc/bls24-317/internal/fptower/e12.go index 315432f75b..785fea776d 100644 --- a/ecc/bls24-317/internal/fptower/e12.go +++ b/ecc/bls24-317/internal/fptower/e12.go @@ -240,23 +240,49 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } z.Set(&res) + return z } diff --git a/ecc/bls24-317/internal/fptower/e12_test.go b/ecc/bls24-317/internal/fptower/e12_test.go index 19adb05dd2..76d4a6c9e0 100644 --- a/ecc/bls24-317/internal/fptower/e12_test.go +++ b/ecc/bls24-317/internal/fptower/e12_test.go @@ -248,6 +248,6 @@ func BenchmarkE12ExpBySeed(b *testing.B) { _, _ = a.SetRandom() b.ResetTimer() for i := 0; i < b.N; i++ { - a.Exp(&a, seed).Conjugate(&a) + a.Exp(a, &seed).Conjugate(&a) } } diff --git a/ecc/bls24-317/internal/fptower/e2.go b/ecc/bls24-317/internal/fptower/e2.go index 25d035ea80..688d71776b 100644 --- a/ecc/bls24-317/internal/fptower/e2.go +++ b/ecc/bls24-317/internal/fptower/e2.go @@ -162,10 +162,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bls24-317/internal/fptower/e24.go b/ecc/bls24-317/internal/fptower/e24.go index 9792420ca6..c29a23021e 100644 --- a/ecc/bls24-317/internal/fptower/e24.go +++ b/ecc/bls24-317/internal/fptower/e24.go @@ -18,9 +18,18 @@ package fptower import ( "errors" + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E24 is a degree two finite field extension of fp6 type E24 struct { D0, D1 E12 @@ -144,25 +153,25 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { var t [7]E4 - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.D0.C1) - // t1 = g5^2 + // t1 = g5² t[1].Square(&x.D1.C2) // t5 = g1 + g5 t[5].Add(&x.D0.C1, &x.D1.C2) - // t2 = (g1 + g5)^2 + // t2 = (g1 + g5)² t[2].Square(&t[5]) - // t3 = g1^2 + g5^2 + // t3 = g1² + g5² t[3].Add(&t[0], &t[1]) // t5 = 2 * g1 * g5 t[5].Sub(&t[2], &t[3]) // t6 = g3 + g2 t[6].Add(&x.D1.C0, &x.D0.C2) - // t3 = (g3 + g2)^2 + // t3 = (g3 + g2)² t[3].Square(&t[6]) - // t2 = g3^2 + // t2 = g3² t[2].Square(&x.D1.C0) // t6 = 2 * nr * g1 * g5 @@ -173,33 +182,33 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { // z3 = 6 * nr * g1 * g5 + 2 * g3 z.D1.C0.Add(&t[5], &t[6]) - // t4 = nr * g5^2 + // t4 = nr * g5² t[4].MulByNonResidue(&t[1]) - // t5 = nr * g5^2 + g1^2 + // t5 = nr * g5² + g1² t[5].Add(&t[0], &t[4]) - // t6 = nr * g5^2 + g1^2 - g2 + // t6 = nr * g5² + g1² - g2 t[6].Sub(&t[5], &x.D0.C2) - // t1 = g2^2 + // t1 = g2² t[1].Square(&x.D0.C2) - // t6 = 2 * nr * g5^2 + 2 * g1^2 - 2*g2 + // t6 = 2 * nr * g5² + 2 * g1² - 2*g2 t[6].Double(&t[6]) - // z2 = 3 * nr * g5^2 + 3 * g1^2 - 2*g2 + // z2 = 3 * nr * g5² + 3 * g1² - 2*g2 z.D0.C2.Add(&t[6], &t[5]) - // t4 = nr * g2^2 + // t4 = nr * g2² t[4].MulByNonResidue(&t[1]) - // t5 = g3^2 + nr * g2^2 + // t5 = g3² + nr * g2² t[5].Add(&t[2], &t[4]) - // t6 = g3^2 + nr * g2^2 - g1 + // t6 = g3² + nr * g2² - g1 t[6].Sub(&t[5], &x.D0.C1) - // t6 = 2 * g3^2 + 2 * nr * g2^2 - 2 * g1 + // t6 = 2 * g3² + 2 * nr * g2² - 2 * g1 t[6].Double(&t[6]) - // z1 = 3 * g3^2 + 3 * nr * g2^2 - 2 * g1 + // z1 = 3 * g3² + 3 * nr * g2² - 2 * g1 z.D0.C1.Add(&t[6], &t[5]) - // t0 = g2^2 + g3^2 + // t0 = g2² + g3² t[0].Add(&t[2], &t[1]) // t5 = 2 * g3 * g2 t[5].Sub(&t[3], &t[0]) @@ -220,13 +229,13 @@ func (z *E24) DecompressKarabina(x *E24) *E24 { var one E4 one.SetOne() - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.D0.C1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t[1].Sub(&t[0], &x.D0.C2). Double(&t[1]). Add(&t[1], &t[0]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t[2].Square(&x.D1.C2) t[0].MulByNonResidue(&t[2]). Add(&t[0], &t[1]) @@ -239,14 +248,14 @@ func (z *E24) DecompressKarabina(x *E24) *E24 { // t1 = g2 * g1 t[1].Mul(&x.D0.C2, &x.D0.C1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t[2].Square(&x.D1.C1). Sub(&t[2], &t[1]). Double(&t[2]). Sub(&t[2], &t[1]) // t1 = g3 * g5 t[1].Mul(&x.D1.C0, &x.D1.C2) - // c_0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // c₀ = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t[2].Add(&t[2], &t[1]) z.D0.C0.MulByNonResidue(&t[2]). Add(&z.D0.C0, &one) @@ -275,13 +284,13 @@ func BatchDecompressKarabina(x []E24) []E24 { one.SetOne() for i := 0; i < n; i++ { - // t0 = g1^2 + // t0 = g1² t0[i].Square(&x[i].D0.C1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t1[i].Sub(&t0[i], &x[i].D0.C2). Double(&t1[i]). Add(&t1[i], &t0[i]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t2[i].Square(&x[i].D1.C2) t0[i].MulByNonResidue(&t2[i]). Add(&t0[i], &t1[i]) @@ -298,7 +307,7 @@ func BatchDecompressKarabina(x []E24) []E24 { // t1 = g2 * g1 t1[i].Mul(&x[i].D0.C2, &x[i].D0.C1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t2[i].Square(&x[i].D1.C1) t2[i].Sub(&t2[i], &t1[i]) t2[i].Double(&t2[i]) @@ -306,7 +315,7 @@ func BatchDecompressKarabina(x []E24) []E24 { // t1 = g3 * g5 t1[i].Mul(&x[i].D1.C0, &x[i].D1.C2) - // z0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // z0 = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t2[i].Add(&t2[i], &t1[i]) x[i].D0.C0.MulByNonResidue(&t2[i]). Add(&x[i].D0.C0, &one) @@ -319,10 +328,10 @@ func BatchDecompressKarabina(x []E24) []E24 { // https://eprint.iacr.org/2009/565.pdf, 3.2 func (z *E24) CyclotomicSquare(x *E24) *E24 { - // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E4^6 - // cyclosquare(x)=(3*x4^2*u + 3*x0^2 - 2*x0, - // 3*x2^2*u + 3*x3^2 - 2*x1, - // 3*x5^2*u + 3*x1^2 - 2*x2, + // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E4⁶ + // cyclosquare(x)=(3*x4²*u + 3*x0² - 2*x0, + // 3*x2²*u + 3*x3² - 2*x1, + // 3*x5²*u + 3*x1² - 2*x2, // 6*x1*x5*u + 2*x3, // 6*x0*x4 + 2*x4, // 6*x2*x3 + 2*x5) @@ -339,9 +348,9 @@ func (z *E24) CyclotomicSquare(x *E24) *E24 { t[5].Square(&x.D0.C1) t[8].Add(&x.D1.C2, &x.D0.C1).Square(&t[8]).Sub(&t[8], &t[4]).Sub(&t[8], &t[5]).MulByNonResidue(&t[8]) // 2*x5*x1*u - t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4^2*u + x0^2 - t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2^2*u + x3^2 - t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5^2*u + x1^2 + t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4²*u + x0² + t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2²*u + x3² + t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5²*u + x1² z.D0.C0.Sub(&t[0], &x.D0.C0).Double(&z.D0.C0).Add(&z.D0.C0, &t[0]) z.D0.C1.Sub(&t[2], &x.D0.C1).Double(&z.D0.C1).Add(&z.D0.C1, &t[2]) @@ -404,22 +413,171 @@ func BatchInvertE24(a []E24) []E24 { return res } -// Exp sets z=x**e and returns it -func (z *E24) Exp(x *E24, e big.Int) *E24 { +// Exp sets z=xᵏ (mod q²⁴) and returns it +// uses 2-bits windowed method +func (z *E24) Exp(x E24, k *big.Int) *E24 { + 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) + } + var res E24 + var ops [3]E24 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q²⁴) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E24) CyclotomicExp(x E24, k *big.Int) *E24 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q²⁴) == (x⁻¹)ᵏ (mod q²⁴) + x.Conjugate(&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) + } + + var res, xInv E24 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q²⁴) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E24) ExpGLV(x E24, k *big.Int) *E24 { + 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.Conjugate(&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) + } + + var table [15]E24 + var res E24 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1)/2 + 1; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } @@ -635,10 +793,10 @@ func (z *E24) IsInSubGroup() bool { // CompressTorus GT/E24 element to half its size // z must be in the cyclotomic subgroup -// i.e. z^(p^4-p^2+1)=1 +// i.e. z^(p⁴-p²+1)=1 // e.g. GT // "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG -// z.C1 == 0 only when z \in {-1,1} +// z.C1 == 0 only when z ∈ {-1,1} func (z *E24) CompressTorus() (E12, error) { if z.D1.IsZero() { diff --git a/ecc/bls24-317/internal/fptower/e24_test.go b/ecc/bls24-317/internal/fptower/e24_test.go index b39bf90642..6f235ca829 100644 --- a/ecc/bls24-317/internal/fptower/e24_test.go +++ b/ecc/bls24-317/internal/fptower/e24_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bls24-317/fp" @@ -192,6 +193,7 @@ func TestE24Ops(t *testing.T) { genA := GenE24() genB := GenE24() + genExp := GenFp() properties.Property("[BLS24-317] sub & add should leave an element invariant", prop.ForAll( func(a, b *E24) bool { @@ -406,13 +408,36 @@ func TestE24Ops(t *testing.T) { genA, )) + properties.Property("[BLS24-315] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E24, e fp.Element) bool { + var b, c, d E24 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(24) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BLS24-317] Frobenius of x in E24 should be equal to x^q", prop.ForAll( func(a *E24) bool { var b, c E24 q := fp.Modulus() b.Frobenius(a) c.Set(a) - c.Exp(&c, *q) + c.Exp(c, q) return c.Equal(&b) }, genA, @@ -423,7 +448,7 @@ func TestE24Ops(t *testing.T) { var b, c E24 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, @@ -434,7 +459,7 @@ func TestE24Ops(t *testing.T) { var b, c E24 q := fp.Modulus() b.FrobeniusQuad(a) - c.Exp(a, *q).Exp(&c, *q).Exp(&c, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q).Exp(c, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls24-317/internal/fptower/e4.go b/ecc/bls24-317/internal/fptower/e4.go index 2c84e6d1c3..63e6b37321 100644 --- a/ecc/bls24-317/internal/fptower/e4.go +++ b/ecc/bls24-317/internal/fptower/e4.go @@ -160,7 +160,7 @@ func (z *E4) MulByNonResidue(x *E4) *E4 { return z } -// MulByNonResidueInv mul x by (0,1)^{-1} +// MulByNonResidueInv mul x by (0,1)⁻¹ func (z *E4) MulByNonResidueInv(x *E4) *E4 { a := x.B1 var uInv E2 @@ -216,23 +216,37 @@ func (z *E4) Inverse(x *E4) *E4 { return z } -// Exp sets z=x**e and returns it -func (z *E4) Exp(x *E4, e big.Int) *E4 { - var res E4 - res.SetOne() +// 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 := range b { + for i := 0; i < len(b); i++ { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + for j := 0; j < 8; j++ { + z.Square(z) + if (w & (0b10000000 >> j)) != 0 { + z.Mul(z, &x) } - mask = mask >> 1 } } - z.Set(&res) + return z } @@ -283,13 +297,13 @@ func (z *E4) Sqrt(x *E4) *E4 { var exp, one big.Int one.SetUint64(1) exp.Mul(q, q).Sub(&exp, &one).Rsh(&exp, 1) - d.Exp(&c, exp) + 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.Exp(*x, &exp) b.norm(&_b) o.SetOne() if _b.Equal(&o) { diff --git a/ecc/bls24-317/internal/fptower/e4_test.go b/ecc/bls24-317/internal/fptower/e4_test.go index 0afe602673..f0f9932b52 100644 --- a/ecc/bls24-317/internal/fptower/e4_test.go +++ b/ecc/bls24-317/internal/fptower/e4_test.go @@ -257,7 +257,7 @@ func TestE4Ops(t *testing.T) { var b, c E4 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, diff --git a/ecc/bls24-317/internal/fptower/parameters.go b/ecc/bls24-317/internal/fptower/parameters.go new file mode 100644 index 0000000000..6d637e8624 --- /dev/null +++ b/ecc/bls24-317/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("3640754176", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bls24-317/pairing_test.go b/ecc/bls24-317/pairing_test.go index 0f9a0a75a2..23e7792c8f 100644 --- a/ecc/bls24-317/pairing_test.go +++ b/ecc/bls24-317/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bls24-317/fp" "github.com/consensys/gnark-crypto/ecc/bls24-317/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -45,6 +46,7 @@ func TestPairing(t *testing.T) { genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BLS24-317] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -64,6 +66,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BLS24-317] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BLS24-317] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -75,7 +101,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -102,9 +128,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -353,3 +379,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bn254/internal/fptower/e12.go b/ecc/bn254/internal/fptower/e12.go index 10b9ecc1fe..950d60de6c 100644 --- a/ecc/bn254/internal/fptower/e12.go +++ b/ecc/bn254/internal/fptower/e12.go @@ -19,10 +19,19 @@ package fptower import ( "encoding/binary" "errors" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" "math/big" + "sync" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E12 is a degree two finite field extension of fp6 type E12 struct { C0, C1 E6 @@ -406,22 +415,171 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q¹²) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E12) CyclotomicExp(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var res, xInv E12 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q¹²) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E12) ExpGLV(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var table [15]E12 + var res E12 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } diff --git a/ecc/bn254/internal/fptower/e12_pairing.go b/ecc/bn254/internal/fptower/e12_pairing.go index 9b36b67816..a4abaf510d 100644 --- a/ecc/bn254/internal/fptower/e12_pairing.go +++ b/ecc/bn254/internal/fptower/e12_pairing.go @@ -12,7 +12,7 @@ func (z *E12) nSquareCompressed(n int) { } } -// Expt set z to xᵗ in E12 and return z (t is the generator of the curve) +// Expt set z to xᵗ (mod q¹²) and return z (t is the generator of the curve) func (z *E12) Expt(x *E12) *E12 { // Expt computation is derived from the addition chain: // diff --git a/ecc/bn254/internal/fptower/e12_test.go b/ecc/bn254/internal/fptower/e12_test.go index 840308fcde..a503e238cd 100644 --- a/ecc/bn254/internal/fptower/e12_test.go +++ b/ecc/bn254/internal/fptower/e12_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bn254/fp" @@ -195,6 +196,7 @@ func TestE12Ops(t *testing.T) { genA := GenE12() genB := GenE12() + genExp := GenFp() properties.Property("[BN254] sub & add should leave an element invariant", prop.ForAll( func(a, b *E12) bool { @@ -375,12 +377,35 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BN254] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E12, e fp.Element) bool { + var b, c, d E12 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BN254] Frobenius of x in E12 should be equal to x^q", prop.ForAll( func(a *E12) bool { var b, c E12 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, @@ -391,7 +416,7 @@ func TestE12Ops(t *testing.T) { var b, c E12 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA, diff --git a/ecc/bn254/internal/fptower/e2.go b/ecc/bn254/internal/fptower/e2.go index fe7e11343c..3d12b8b7e8 100644 --- a/ecc/bn254/internal/fptower/e2.go +++ b/ecc/bn254/internal/fptower/e2.go @@ -170,10 +170,27 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() + b := e.Bytes() for i := 0; i < len(b); i++ { w := b[i] for j := 0; j < 8; j++ { diff --git a/ecc/bn254/internal/fptower/parameters.go b/ecc/bn254/internal/fptower/parameters.go new file mode 100644 index 0000000000..3859aeac76 --- /dev/null +++ b/ecc/bn254/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bn254/fr" +) + +// generator of the curve +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("147946756881789318990833708069417712966", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bn254/pairing_test.go b/ecc/bn254/pairing_test.go index d89f7db8c5..da33256083 100644 --- a/ecc/bn254/pairing_test.go +++ b/ecc/bn254/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -44,6 +45,7 @@ func TestPairing(t *testing.T) { genA := GenE12() genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BN254] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -63,6 +65,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BN254] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BN254] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -74,7 +100,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -101,9 +127,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -352,3 +378,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bw6-633/internal/fptower/e6.go b/ecc/bw6-633/internal/fptower/e6.go index 105b978239..92f54a1dd3 100644 --- a/ecc/bw6-633/internal/fptower/e6.go +++ b/ecc/bw6-633/internal/fptower/e6.go @@ -19,10 +19,19 @@ package fptower import ( "errors" "math/big" + "sync" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-633/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E6 is a degree two finite field extension of fp3 type E6 struct { B0, B1 E3 @@ -146,25 +155,25 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { var t [7]fp.Element - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = g5^2 + // t1 = g5² t[1].Square(&x.B1.A2) // t5 = g1 + g5 t[5].Add(&x.B0.A1, &x.B1.A2) - // t2 = (g1 + g5)^2 + // t2 = (g1 + g5)² t[2].Square(&t[5]) - // t3 = g1^2 + g5^2 + // t3 = g1² + g5² t[3].Add(&t[0], &t[1]) // t5 = 2 * g1 * g5 t[5].Sub(&t[2], &t[3]) // t6 = g3 + g2 t[6].Add(&x.B1.A0, &x.B0.A2) - // t3 = (g3 + g2)^2 + // t3 = (g3 + g2)² t[3].Square(&t[6]) - // t2 = g3^2 + // t2 = g3² t[2].Square(&x.B1.A0) // t6 = 2 * nr * g1 * g5 @@ -175,33 +184,33 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { // z3 = 6 * nr * g1 * g5 + 2 * g3 z.B1.A0.Add(&t[5], &t[6]) - // t4 = nr * g5^2 + // t4 = nr * g5² t[4].MulByNonResidue(&t[1]) - // t5 = nr * g5^2 + g1^2 + // t5 = nr * g5² + g1² t[5].Add(&t[0], &t[4]) - // t6 = nr * g5^2 + g1^2 - g2 + // t6 = nr * g5² + g1² - g2 t[6].Sub(&t[5], &x.B0.A2) - // t1 = g2^2 + // t1 = g2² t[1].Square(&x.B0.A2) - // t6 = 2 * nr * g5^2 + 2 * g1^2 - 2*g2 + // t6 = 2 * nr * g5² + 2 * g1² - 2*g2 t[6].Double(&t[6]) - // z2 = 3 * nr * g5^2 + 3 * g1^2 - 2*g2 + // z2 = 3 * nr * g5² + 3 * g1² - 2*g2 z.B0.A2.Add(&t[6], &t[5]) - // t4 = nr * g2^2 + // t4 = nr * g2² t[4].MulByNonResidue(&t[1]) - // t5 = g3^2 + nr * g2^2 + // t5 = g3² + nr * g2² t[5].Add(&t[2], &t[4]) - // t6 = g3^2 + nr * g2^2 - g1 + // t6 = g3² + nr * g2² - g1 t[6].Sub(&t[5], &x.B0.A1) - // t6 = 2 * g3^2 + 2 * nr * g2^2 - 2 * g1 + // t6 = 2 * g3² + 2 * nr * g2² - 2 * g1 t[6].Double(&t[6]) - // z1 = 3 * g3^2 + 3 * nr * g2^2 - 2 * g1 + // z1 = 3 * g3² + 3 * nr * g2² - 2 * g1 z.B0.A1.Add(&t[6], &t[5]) - // t0 = g2^2 + g3^2 + // t0 = g2² + g3² t[0].Add(&t[2], &t[1]) // t5 = 2 * g3 * g2 t[5].Sub(&t[3], &t[0]) @@ -222,13 +231,13 @@ func (z *E6) Decompress(x *E6) *E6 { var one fp.Element one.SetOne() - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t[1].Sub(&t[0], &x.B0.A2). Double(&t[1]). Add(&t[1], &t[0]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t[2].Square(&x.B1.A2) t[0].MulByNonResidue(&t[2]). Add(&t[0], &t[1]) @@ -241,14 +250,14 @@ func (z *E6) Decompress(x *E6) *E6 { // t1 = g2 * g1 t[1].Mul(&x.B0.A2, &x.B0.A1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t[2].Square(&x.B1.A1). Sub(&t[2], &t[1]). Double(&t[2]). Sub(&t[2], &t[1]) // t1 = g3 * g5 t[1].Mul(&x.B1.A0, &x.B1.A2) - // c_0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // c₀ = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t[2].Add(&t[2], &t[1]) z.B0.A0.MulByNonResidue(&t[2]). Add(&z.B0.A0, &one) @@ -264,10 +273,10 @@ func (z *E6) Decompress(x *E6) *E6 { // Granger-Scott's cyclotomic square // https://eprint.iacr.org/2009/565.pdf, 3.2 func (z *E6) CyclotomicSquare(x *E6) *E6 { - // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3^6 - // cyclosquare(x)=(3*x4^2*u + 3*x0^2 - 2*x0, - // 3*x2^2*u + 3*x3^2 - 2*x1, - // 3*x5^2*u + 3*x1^2 - 2*x2, + // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3⁶ + // cyclosquare(x)=(3*x4²*u + 3*x0² - 2*x0, + // 3*x2²*u + 3*x3² - 2*x1, + // 3*x5²*u + 3*x1² - 2*x2, // 6*x1*x5*u + 2*x3, // 6*x0*x4 + 2*x4, // 6*x2*x3 + 2*x5) @@ -284,9 +293,9 @@ func (z *E6) CyclotomicSquare(x *E6) *E6 { t[5].Square(&x.B0.A1) t[8].Add(&x.B1.A2, &x.B0.A1).Square(&t[8]).Sub(&t[8], &t[4]).Sub(&t[8], &t[5]).MulByNonResidue(&t[8]) // 2*x5*x1*u - t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4^2*u + x0^2 - t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2^2*u + x3^2 - t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5^2*u + x1^2 + t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4²*u + x0² + t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2²*u + x3² + t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5²*u + x1² z.B0.A0.Sub(&t[0], &x.B0.A0).Double(&z.B0.A0).Add(&z.B0.A0, &t[0]) z.B0.A1.Sub(&t[2], &x.B0.A1).Double(&z.B0.A1).Add(&z.B0.A1, &t[2]) @@ -349,22 +358,171 @@ func BatchInvertE6(a []E6) []E6 { return res } -// Exp sets z=x**e and returns it -func (z *E6) Exp(x *E6, e big.Int) *E6 { +// Exp sets z=xᵏ (mod q⁶) and returns it +// uses 2-bits windowed method +func (z *E6) Exp(x E6, k *big.Int) *E6 { + 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) + } + var res E6 + var ops [3]E6 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q⁶) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E6) CyclotomicExp(x E6, k *big.Int) *E6 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q⁶) == (x⁻¹)ᵏ (mod q⁶) + x.Conjugate(&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) + } + + var res, xInv E6 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q⁶) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E6) ExpGLV(x E6, k *big.Int) *E6 { + 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.Conjugate(&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) + } + + var table [15]E6 + var res E6 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } @@ -460,9 +618,9 @@ func (z *E6) IsInSubGroup() bool { _a.Frobenius(z) a.CyclotomicSquare(&_a).Mul(&a, &_a) // z^(3p) - // t(x)-1 = (-10-4x-13x^2+6x^3+7x^4-23x^5+19x^6-12x^7+2x^8+11x^9-7x^10)/3 - t[0].CyclotomicSquare(z) // z^2 - t[1].CyclotomicSquare(&t[0]) // z^4 + // t(x)-1 = (-10-4x-13x²+6x³+7x⁴-23x⁵+19x⁶-12x⁷+2x⁸+11x⁹-7x¹⁰)/3 + t[0].CyclotomicSquare(z) // z² + t[1].CyclotomicSquare(&t[0]) // z⁴ t[2].CyclotomicSquare(&t[1]). Mul(&t[2], &t[0]). Conjugate(&t[2]) // *z^(-10) @@ -472,52 +630,52 @@ func (z *E6) IsInSubGroup() bool { Mul(&t[4], &t[2]). Mul(&t[4], z). Expt(&t[4]). - Expt(&t[4]) // *z^(-13u^2) + Expt(&t[4]) // *z^(-13u²) t[5].Mul(&t[0], &t[1]). Expt(&t[5]). Expt(&t[5]). - Expt(&t[5]) // *z^(6u^3) + Expt(&t[5]) // *z^(6u³) tmp.Expt(z). Expt(&tmp). - Expt(&tmp) // z^(u^3) + Expt(&tmp) // z^(u³) t[6].Mul(&tmp, &t[5]). - Expt(&t[6]) // *z^(7u^4) + Expt(&t[6]) // *z^(7u⁴) t[7].CyclotomicSquare(&t[5]). - CyclotomicSquare(&t[7]) // z^(24u^3) - tmp.Conjugate(&tmp) // z^(-u^3) + CyclotomicSquare(&t[7]) // z^(24u³) + tmp.Conjugate(&tmp) // z^(-u³) t[7].Mul(&t[7], &tmp). Conjugate(&t[7]). Expt(&t[7]). - Expt(&t[7]) // *z^(-23u^5) + Expt(&t[7]) // *z^(-23u⁵) t[8].Conjugate(&t[4]). Expt(&t[8]). Mul(&t[8], &t[5]). Expt(&t[8]). Expt(&t[8]). - Expt(&t[8]) // *z^(19u^6) + Expt(&t[8]) // *z^(19u⁶) t[9].Conjugate(&t[5]). CyclotomicSquare(&t[9]). Expt(&t[9]). Expt(&t[9]). Expt(&t[9]). - Expt(&t[9]) // *z^(-12u^7) + Expt(&t[9]) // *z^(-12u⁷) tmp.Expt(&t[7]). - Expt(&tmp) // z^(-23u^7) + Expt(&tmp) // z^(-23u⁷) t[10].Conjugate(&t[9]). CyclotomicSquare(&t[10]). - Mul(&t[10], &tmp) // z^(u^7) + Mul(&t[10], &tmp) // z^(u⁷) t[11].Mul(&t[9], &t[10]). Conjugate(&t[11]). Expt(&t[11]). - Expt(&t[11]) // *z^(11u^9) + Expt(&t[11]) // *z^(11u⁹) t[10].Expt(&t[10]). - CyclotomicSquare(&t[10]) // *z^(2u^8) + CyclotomicSquare(&t[10]) // *z^(2u⁸) t[12].Conjugate(&t[10]). CyclotomicSquare(&t[12]). Expt(&t[12]). Mul(&t[12], &t[11]). Expt(&t[12]). - Conjugate(&t[12]) // *z^(-7u^10) + Conjugate(&t[12]) // *z^(-7u¹⁰) b.Mul(&t[2], &t[3]). Mul(&b, &t[4]). @@ -535,10 +693,10 @@ func (z *E6) IsInSubGroup() bool { // CompressTorus GT/E6 element to half its size // z must be in the cyclotomic subgroup -// i.e. z^(p^4-p^2+1)=1 +// i.e. z^(p⁴-p²+1)=1 // e.g. GT // "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG -// z.B1 == 0 only when z \in {-1,1} +// z.B1 == 0 only when z ∈ {-1,1} func (z *E6) CompressTorus() (E3, error) { if z.B1.IsZero() { diff --git a/ecc/bw6-633/internal/fptower/e6_test.go b/ecc/bw6-633/internal/fptower/e6_test.go index faa5d21495..8bda2d0922 100644 --- a/ecc/bw6-633/internal/fptower/e6_test.go +++ b/ecc/bw6-633/internal/fptower/e6_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bw6-633/fp" @@ -172,6 +173,7 @@ func TestE6Ops(t *testing.T) { genA := GenE6() genB := GenE6() + genExp := GenFp() properties.Property("[BW6-633] sub & add should leave an element invariant", prop.ForAll( func(a, b *E6) bool { @@ -312,12 +314,35 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BW6-633] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E6, e fp.Element) bool { + var b, c, d E6 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(6) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BW6-633] Frobenius of x in E6 should be equal to x^q", prop.ForAll( func(a *E6) bool { var b, c E6 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, diff --git a/ecc/bw6-633/internal/fptower/parameters.go b/ecc/bw6-633/internal/fptower/parameters.go new file mode 100644 index 0000000000..308498b0c6 --- /dev/null +++ b/ecc/bw6-633/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" +) + +// t-1 +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("37014442673353839783463348892746893664389658635873267609916377398480286678854893830143", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bw6-633/pairing_test.go b/ecc/bw6-633/pairing_test.go index 51224c3285..f8a0f84bbf 100644 --- a/ecc/bw6-633/pairing_test.go +++ b/ecc/bw6-633/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bw6-633/fp" "github.com/consensys/gnark-crypto/ecc/bw6-633/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -45,6 +46,7 @@ func TestPairing(t *testing.T) { genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BW6-633] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -64,6 +66,31 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BW6-633] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(6) + + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BW6-633] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -75,7 +102,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -102,9 +129,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -353,3 +380,40 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(6) + + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bw6-756/internal/fptower/e6.go b/ecc/bw6-756/internal/fptower/e6.go index 3d172327fb..35d6b82a76 100644 --- a/ecc/bw6-756/internal/fptower/e6.go +++ b/ecc/bw6-756/internal/fptower/e6.go @@ -19,11 +19,19 @@ package fptower import ( "errors" "math/big" + "sync" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-756/fp" "github.com/consensys/gnark-crypto/ecc/bw6-756/fr" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E6 is a degree two finite field extension of fp3 type E6 struct { B0, B1 E3 @@ -146,25 +154,25 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { var t [7]fp.Element - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = g5^2 + // t1 = g5² t[1].Square(&x.B1.A2) // t5 = g1 + g5 t[5].Add(&x.B0.A1, &x.B1.A2) - // t2 = (g1 + g5)^2 + // t2 = (g1 + g5)² t[2].Square(&t[5]) - // t3 = g1^2 + g5^2 + // t3 = g1² + g5² t[3].Add(&t[0], &t[1]) // t5 = 2 * g1 * g5 t[5].Sub(&t[2], &t[3]) // t6 = g3 + g2 t[6].Add(&x.B1.A0, &x.B0.A2) - // t3 = (g3 + g2)^2 + // t3 = (g3 + g2)² t[3].Square(&t[6]) - // t2 = g3^2 + // t2 = g3² t[2].Square(&x.B1.A0) // t6 = 2 * nr * g1 * g5 @@ -175,33 +183,33 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { // z3 = 6 * nr * g1 * g5 + 2 * g3 z.B1.A0.Add(&t[5], &t[6]) - // t4 = nr * g5^2 + // t4 = nr * g5² t[4].MulByNonResidue(&t[1]) - // t5 = nr * g5^2 + g1^2 + // t5 = nr * g5² + g1² t[5].Add(&t[0], &t[4]) - // t6 = nr * g5^2 + g1^2 - g2 + // t6 = nr * g5² + g1² - g2 t[6].Sub(&t[5], &x.B0.A2) - // t1 = g2^2 + // t1 = g2² t[1].Square(&x.B0.A2) - // t6 = 2 * nr * g5^2 + 2 * g1^2 - 2*g2 + // t6 = 2 * nr * g5² + 2 * g1² - 2*g2 t[6].Double(&t[6]) - // z2 = 3 * nr * g5^2 + 3 * g1^2 - 2*g2 + // z2 = 3 * nr * g5² + 3 * g1² - 2*g2 z.B0.A2.Add(&t[6], &t[5]) - // t4 = nr * g2^2 + // t4 = nr * g2² t[4].MulByNonResidue(&t[1]) - // t5 = g3^2 + nr * g2^2 + // t5 = g3² + nr * g2² t[5].Add(&t[2], &t[4]) - // t6 = g3^2 + nr * g2^2 - g1 + // t6 = g3² + nr * g2² - g1 t[6].Sub(&t[5], &x.B0.A1) - // t6 = 2 * g3^2 + 2 * nr * g2^2 - 2 * g1 + // t6 = 2 * g3² + 2 * nr * g2² - 2 * g1 t[6].Double(&t[6]) - // z1 = 3 * g3^2 + 3 * nr * g2^2 - 2 * g1 + // z1 = 3 * g3² + 3 * nr * g2² - 2 * g1 z.B0.A1.Add(&t[6], &t[5]) - // t0 = g2^2 + g3^2 + // t0 = g2² + g3² t[0].Add(&t[2], &t[1]) // t5 = 2 * g3 * g2 t[5].Sub(&t[3], &t[0]) @@ -222,13 +230,13 @@ func (z *E6) Decompress(x *E6) *E6 { var one fp.Element one.SetOne() - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t[1].Sub(&t[0], &x.B0.A2). Double(&t[1]). Add(&t[1], &t[0]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t[2].Square(&x.B1.A2) t[0].MulByNonResidue(&t[2]). Add(&t[0], &t[1]) @@ -241,14 +249,14 @@ func (z *E6) Decompress(x *E6) *E6 { // t1 = g2 * g1 t[1].Mul(&x.B0.A2, &x.B0.A1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t[2].Square(&x.B1.A1). Sub(&t[2], &t[1]). Double(&t[2]). Sub(&t[2], &t[1]) // t1 = g3 * g5 t[1].Mul(&x.B1.A0, &x.B1.A2) - // c_0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // c₀ = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t[2].Add(&t[2], &t[1]) z.B0.A0.MulByNonResidue(&t[2]). Add(&z.B0.A0, &one) @@ -264,10 +272,10 @@ func (z *E6) Decompress(x *E6) *E6 { // Granger-Scott's cyclotomic square // https://eprint.iacr.org/2009/565.pdf, 3.2 func (z *E6) CyclotomicSquare(x *E6) *E6 { - // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3^6 - // cyclosquare(x)=(3*x4^2*u + 3*x0^2 - 2*x0, - // 3*x2^2*u + 3*x3^2 - 2*x1, - // 3*x5^2*u + 3*x1^2 - 2*x2, + // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3⁶ + // cyclosquare(x)=(3*x4²*u + 3*x0² - 2*x0, + // 3*x2²*u + 3*x3² - 2*x1, + // 3*x5²*u + 3*x1² - 2*x2, // 6*x1*x5*u + 2*x3, // 6*x0*x4 + 2*x4, // 6*x2*x3 + 2*x5) @@ -284,9 +292,9 @@ func (z *E6) CyclotomicSquare(x *E6) *E6 { t[5].Square(&x.B0.A1) t[8].Add(&x.B1.A2, &x.B0.A1).Square(&t[8]).Sub(&t[8], &t[4]).Sub(&t[8], &t[5]).MulByNonResidue(&t[8]) // 2*x5*x1*u - t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4^2*u + x0^2 - t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2^2*u + x3^2 - t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5^2*u + x1^2 + t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4²*u + x0² + t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2²*u + x3² + t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5²*u + x1² z.B0.A0.Sub(&t[0], &x.B0.A0).Double(&z.B0.A0).Add(&z.B0.A0, &t[0]) z.B0.A1.Sub(&t[2], &x.B0.A1).Double(&z.B0.A1).Add(&z.B0.A1, &t[2]) @@ -349,22 +357,171 @@ func BatchInvertE6(a []E6) []E6 { return res } -// Exp sets z=x**e and returns it -func (z *E6) Exp(x *E6, e big.Int) *E6 { +// Exp sets z=xᵏ (mod q⁶) and returns it +// uses 2-bits windowed method +func (z *E6) Exp(x E6, k *big.Int) *E6 { + 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) + } + var res E6 + var ops [3]E6 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q⁶) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E6) CyclotomicExp(x E6, k *big.Int) *E6 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q⁶) == (x⁻¹)ᵏ (mod q⁶) + x.Conjugate(&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) + } + + var res, xInv E6 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q⁶) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E6) ExpGLV(x E6, k *big.Int) *E6 { + 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.Conjugate(&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) + } + + var table [15]E6 + var res E6 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } @@ -443,19 +600,20 @@ func (z *E6) SetBytes(e []byte) error { } // IsInSubGroup ensures GT/E6 is in correct sugroup +// TODO: optimize func (z *E6) IsInSubGroup() bool { var one, _z E6 one.SetOne() - _z.Exp(z, *fr.Modulus()) + _z.Exp(*z, fr.Modulus()) return _z.Equal(&one) } // CompressTorus GT/E6 element to half its size // z must be in the cyclotomic subgroup -// i.e. z^(p^4-p^2+1)=1 +// i.e. z^(p⁴-p²+1)=1 // e.g. GT // "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG -// z.B1 == 0 only when z \in {-1,1} +// z.B1 == 0 only when z ∈ {-1,1} func (z *E6) CompressTorus() (E3, error) { if z.B1.IsZero() { diff --git a/ecc/bw6-756/internal/fptower/e6_test.go b/ecc/bw6-756/internal/fptower/e6_test.go index b74c1943bc..58048b0e8b 100644 --- a/ecc/bw6-756/internal/fptower/e6_test.go +++ b/ecc/bw6-756/internal/fptower/e6_test.go @@ -317,7 +317,7 @@ func TestE6Ops(t *testing.T) { var b, c E6 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, diff --git a/ecc/bw6-756/internal/fptower/parameters.go b/ecc/bw6-756/internal/fptower/parameters.go new file mode 100644 index 0000000000..8a8ce6f783 --- /dev/null +++ b/ecc/bw6-756/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bw6-756/fr" +) + +// t-1 +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("164391353554439166353793911729193406645071739502673898176639736370075683438438023898983435337730", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bw6-756/pairing_test.go b/ecc/bw6-756/pairing_test.go index a3d659f3b3..5c111bbd45 100644 --- a/ecc/bw6-756/pairing_test.go +++ b/ecc/bw6-756/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bw6-756/fp" "github.com/consensys/gnark-crypto/ecc/bw6-756/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -45,6 +46,7 @@ func TestPairing(t *testing.T) { genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BW6-756] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -64,6 +66,30 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BW6-756] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BW6-756] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -75,7 +101,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -102,9 +128,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -353,3 +379,39 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/ecc/bw6-761/internal/fptower/e3.go b/ecc/bw6-761/internal/fptower/e3.go index 66010a729d..d30ca85df9 100644 --- a/ecc/bw6-761/internal/fptower/e3.go +++ b/ecc/bw6-761/internal/fptower/e3.go @@ -16,7 +16,7 @@ import ( "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" ) -// E3 is a degree-three finite field extension of fp2 +// E3 is a degree-three finite field extension of fp3 type E3 struct { A0, A1, A2 fp.Element } @@ -27,7 +27,7 @@ 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 stringf +// SetString sets a E3 elmt from string func (z *E3) SetString(s1, s2, s3 string) *E3 { z.A0.SetString(s1) z.A1.SetString(s2) diff --git a/ecc/bw6-761/internal/fptower/e6.go b/ecc/bw6-761/internal/fptower/e6.go index b8056ee7a2..f211c4a0c7 100644 --- a/ecc/bw6-761/internal/fptower/e6.go +++ b/ecc/bw6-761/internal/fptower/e6.go @@ -19,10 +19,19 @@ package fptower import ( "errors" "math/big" + "sync" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E6 is a degree two finite field extension of fp3 type E6 struct { B0, B1 E3 @@ -145,25 +154,25 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { var t [7]fp.Element - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = g5^2 + // t1 = g5² t[1].Square(&x.B1.A2) // t5 = g1 + g5 t[5].Add(&x.B0.A1, &x.B1.A2) - // t2 = (g1 + g5)^2 + // t2 = (g1 + g5)² t[2].Square(&t[5]) - // t3 = g1^2 + g5^2 + // t3 = g1² + g5² t[3].Add(&t[0], &t[1]) // t5 = 2 * g1 * g5 t[5].Sub(&t[2], &t[3]) // t6 = g3 + g2 t[6].Add(&x.B1.A0, &x.B0.A2) - // t3 = (g3 + g2)^2 + // t3 = (g3 + g2)² t[3].Square(&t[6]) - // t2 = g3^2 + // t2 = g3² t[2].Square(&x.B1.A0) // t6 = 2 * nr * g1 * g5 @@ -174,33 +183,33 @@ func (z *E6) CyclotomicSquareCompressed(x *E6) *E6 { // z3 = 6 * nr * g1 * g5 + 2 * g3 z.B1.A0.Add(&t[5], &t[6]) - // t4 = nr * g5^2 + // t4 = nr * g5² t[4].MulByNonResidue(&t[1]) - // t5 = nr * g5^2 + g1^2 + // t5 = nr * g5² + g1² t[5].Add(&t[0], &t[4]) - // t6 = nr * g5^2 + g1^2 - g2 + // t6 = nr * g5² + g1² - g2 t[6].Sub(&t[5], &x.B0.A2) - // t1 = g2^2 + // t1 = g2² t[1].Square(&x.B0.A2) - // t6 = 2 * nr * g5^2 + 2 * g1^2 - 2*g2 + // t6 = 2 * nr * g5² + 2 * g1² - 2*g2 t[6].Double(&t[6]) - // z2 = 3 * nr * g5^2 + 3 * g1^2 - 2*g2 + // z2 = 3 * nr * g5² + 3 * g1² - 2*g2 z.B0.A2.Add(&t[6], &t[5]) - // t4 = nr * g2^2 + // t4 = nr * g2² t[4].MulByNonResidue(&t[1]) - // t5 = g3^2 + nr * g2^2 + // t5 = g3² + nr * g2² t[5].Add(&t[2], &t[4]) - // t6 = g3^2 + nr * g2^2 - g1 + // t6 = g3² + nr * g2² - g1 t[6].Sub(&t[5], &x.B0.A1) - // t6 = 2 * g3^2 + 2 * nr * g2^2 - 2 * g1 + // t6 = 2 * g3² + 2 * nr * g2² - 2 * g1 t[6].Double(&t[6]) - // z1 = 3 * g3^2 + 3 * nr * g2^2 - 2 * g1 + // z1 = 3 * g3² + 3 * nr * g2² - 2 * g1 z.B0.A1.Add(&t[6], &t[5]) - // t0 = g2^2 + g3^2 + // t0 = g2² + g3² t[0].Add(&t[2], &t[1]) // t5 = 2 * g3 * g2 t[5].Sub(&t[3], &t[0]) @@ -221,13 +230,13 @@ func (z *E6) Decompress(x *E6) *E6 { var one fp.Element one.SetOne() - // t0 = g1^2 + // t0 = g1² t[0].Square(&x.B0.A1) - // t1 = 3 * g1^2 - 2 * g2 + // t1 = 3 * g1² - 2 * g2 t[1].Sub(&t[0], &x.B0.A2). Double(&t[1]). Add(&t[1], &t[0]) - // t0 = E * g5^2 + t1 + // t0 = E * g5² + t1 t[2].Square(&x.B1.A2) t[0].MulByNonResidue(&t[2]). Add(&t[0], &t[1]) @@ -240,14 +249,14 @@ func (z *E6) Decompress(x *E6) *E6 { // t1 = g2 * g1 t[1].Mul(&x.B0.A2, &x.B0.A1) - // t2 = 2 * g4^2 - 3 * g2 * g1 + // t2 = 2 * g4² - 3 * g2 * g1 t[2].Square(&x.B1.A1). Sub(&t[2], &t[1]). Double(&t[2]). Sub(&t[2], &t[1]) // t1 = g3 * g5 t[1].Mul(&x.B1.A0, &x.B1.A2) - // c_0 = E * (2 * g4^2 + g3 * g5 - 3 * g2 * g1) + 1 + // c₀ = E * (2 * g4² + g3 * g5 - 3 * g2 * g1) + 1 t[2].Add(&t[2], &t[1]) z.B0.A0.MulByNonResidue(&t[2]). Add(&z.B0.A0, &one) @@ -263,10 +272,10 @@ func (z *E6) Decompress(x *E6) *E6 { // Granger-Scott's cyclotomic square // https://eprint.iacr.org/2009/565.pdf, 3.2 func (z *E6) CyclotomicSquare(x *E6) *E6 { - // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3^6 - // cyclosquare(x)=(3*x4^2*u + 3*x0^2 - 2*x0, - // 3*x2^2*u + 3*x3^2 - 2*x1, - // 3*x5^2*u + 3*x1^2 - 2*x2, + // x=(x0,x1,x2,x3,x4,x5,x6,x7) in E3⁶ + // cyclosquare(x)=(3*x4²*u + 3*x0² - 2*x0, + // 3*x2²*u + 3*x3² - 2*x1, + // 3*x5²*u + 3*x1² - 2*x2, // 6*x1*x5*u + 2*x3, // 6*x0*x4 + 2*x4, // 6*x2*x3 + 2*x5) @@ -283,9 +292,9 @@ func (z *E6) CyclotomicSquare(x *E6) *E6 { t[5].Square(&x.B0.A1) t[8].Add(&x.B1.A2, &x.B0.A1).Square(&t[8]).Sub(&t[8], &t[4]).Sub(&t[8], &t[5]).MulByNonResidue(&t[8]) // 2*x5*x1*u - t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4^2*u + x0^2 - t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2^2*u + x3^2 - t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5^2*u + x1^2 + t[0].MulByNonResidue(&t[0]).Add(&t[0], &t[1]) // x4²*u + x0² + t[2].MulByNonResidue(&t[2]).Add(&t[2], &t[3]) // x2²*u + x3² + t[4].MulByNonResidue(&t[4]).Add(&t[4], &t[5]) // x5²*u + x1² z.B0.A0.Sub(&t[0], &x.B0.A0).Double(&z.B0.A0).Add(&z.B0.A0, &t[0]) z.B0.A1.Sub(&t[2], &x.B0.A1).Double(&z.B0.A1).Add(&z.B0.A1, &t[2]) @@ -348,22 +357,171 @@ func BatchInvertE6(a []E6) []E6 { return res } -// Exp sets z=x**e and returns it -func (z *E6) Exp(x *E6, e big.Int) *E6 { +// Exp sets z=xᵏ (mod q⁶) and returns it +// uses 2-bits windowed method +func (z *E6) Exp(x E6, k *big.Int) *E6 { + 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) + } + var res E6 + var ops [3]E6 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q⁶) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E6) CyclotomicExp(x E6, k *big.Int) *E6 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q⁶) == (x⁻¹)ᵏ (mod q⁶) + x.Conjugate(&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) + } + + var res, xInv E6 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q⁶) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E6) ExpGLV(x E6, k *big.Int) *E6 { + 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.Conjugate(&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) + } + + var table [15]E6 + var res E6 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } @@ -458,13 +616,13 @@ func (z *E6) IsInSubGroup() bool { _a.Frobenius(z) a.CyclotomicSquare(&_a).Mul(&a, &_a) // z^(3p) - // t(x)-1 = (13x^6 − 23x^5 − 9x^4 + 35x^3 + 10x + 19)/3 - t[0].CyclotomicSquare(z) // z^2 + // t(x)-1 = (13x⁶ − 23x⁵ − 9x⁴ + 35x³ + 10x + 19)/3 + t[0].CyclotomicSquare(z) // z² t[1].CyclotomicSquare(&t[0]). - CyclotomicSquare(&t[1]) // z^8 + CyclotomicSquare(&t[1]) // z⁸ t[2].CyclotomicSquare(&t[1]). Mul(&t[2], &t[0]). - Mul(&t[2], z) // z^19* + Mul(&t[2], z) // z¹⁹* t[3].Mul(&t[0], &t[1]). Expt(&t[3]) // z^(10u)* t[4].CyclotomicSquare(&t[3]). @@ -474,25 +632,25 @@ func (z *E6) IsInSubGroup() bool { Expt(&t[0]) // z^(5u) t[4].Mul(&t[4], &t[0]). Expt(&t[4]). - Expt(&t[4]) // z^(35u^3)* + Expt(&t[4]) // z^(35u³)* t[1].Mul(&t[1], z). Expt(&t[1]). Expt(&t[1]). Expt(&t[1]). Expt(&t[1]). - Conjugate(&t[1]) // z^(-9u^4)* + Conjugate(&t[1]) // z^(-9u⁴)* t[0].Expt(&t[0]). Expt(&t[0]). Expt(&t[0]). - Conjugate(&t[0]) // z^(-5u^4) + Conjugate(&t[0]) // z^(-5u⁴) t[5].CyclotomicSquare(&t[1]). Mul(&t[5], &t[0]). - Expt(&t[5]) // z^(-23u^5)* + Expt(&t[5]) // z^(-23u⁵)* tmp.CyclotomicSquare(&t[1]). - Conjugate(&tmp) // z^(18u^4) + Conjugate(&tmp) // z^(18u⁴) t[0].Mul(&t[0], &tmp). Expt(&t[0]). - Expt(&t[0]) // z^(13u^6)* + Expt(&t[0]) // z^(13u⁶)* b.Mul(&t[2], &t[3]). Mul(&b, &t[4]). @@ -505,10 +663,10 @@ func (z *E6) IsInSubGroup() bool { // CompressTorus GT/E6 element to half its size // z must be in the cyclotomic subgroup -// i.e. z^(p^4-p^2+1)=1 +// i.e. z^(p⁴-p²+1)=1 // e.g. GT // "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG -// z.B1 == 0 only when z \in {-1,1} +// z.B1 == 0 only when z ∈ {-1,1} func (z *E6) CompressTorus() (E3, error) { if z.B1.IsZero() { diff --git a/ecc/bw6-761/internal/fptower/e6_test.go b/ecc/bw6-761/internal/fptower/e6_test.go index 0c9a66787c..4841bb4564 100644 --- a/ecc/bw6-761/internal/fptower/e6_test.go +++ b/ecc/bw6-761/internal/fptower/e6_test.go @@ -17,6 +17,7 @@ package fptower import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" @@ -172,6 +173,7 @@ func TestE6Ops(t *testing.T) { genA := GenE6() genB := GenE6() + genExp := GenFp() properties.Property("[BW6-761] sub & add should leave an element invariant", prop.ForAll( func(a, b *E6) bool { @@ -312,12 +314,35 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BW6-761] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E6, e fp.Element) bool { + var b, c, d E6 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(6) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[BW6-761] Frobenius of x in E6 should be equal to x^q", prop.ForAll( func(a *E6) bool { var b, c E6 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, diff --git a/ecc/bw6-761/internal/fptower/parameters.go b/ecc/bw6-761/internal/fptower/parameters.go new file mode 100644 index 0000000000..8990cd62ea --- /dev/null +++ b/ecc/bw6-761/internal/fptower/parameters.go @@ -0,0 +1,33 @@ +// Copyright 2020 ConsenSys AG +// +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fptower + +import ( + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" +) + +// t-1 +var xGen big.Int + +var glvBasis ecc.Lattice + +func init() { + xGen.SetString("3362637538168598222219435186298528655381674028954528064283340709388076588006567983337308081752755143497537638367247", 10) + _r := fr.Modulus() + ecc.PrecomputeLattice(_r, &xGen, &glvBasis) +} diff --git a/ecc/bw6-761/pairing_test.go b/ecc/bw6-761/pairing_test.go index bda8d1e21a..76bf81eb3a 100644 --- a/ecc/bw6-761/pairing_test.go +++ b/ecc/bw6-761/pairing_test.go @@ -21,6 +21,7 @@ import ( "math/big" "testing" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -45,6 +46,7 @@ func TestPairing(t *testing.T) { genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[BW6-761] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -64,6 +66,31 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[BW6-761] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + + k := new(big.Int).SetUint64(6) + + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[BW6-761] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -75,7 +102,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -102,9 +129,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -353,3 +380,40 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + + k := new(big.Int).SetUint64(6) + + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/internal/generator/pairing/template/tests/pairing.go.tmpl b/internal/generator/pairing/template/tests/pairing.go.tmpl index 0cff77f981..a8209b7ac2 100644 --- a/internal/generator/pairing/template/tests/pairing.go.tmpl +++ b/internal/generator/pairing/template/tests/pairing.go.tmpl @@ -4,6 +4,7 @@ import ( "testing" "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fr" + "github.com/consensys/gnark-crypto/ecc/{{.Name}}/fp" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" ) @@ -32,6 +33,7 @@ func TestPairing(t *testing.T) { {{- end}} genR1 := GenFr() genR2 := GenFr() + genP := GenFp() properties.Property("[{{ toUpper .Name}}] Having the receiver as operand (final expo) should output the same result", prop.ForAll( func(a GT) bool { @@ -51,6 +53,35 @@ func TestPairing(t *testing.T) { genA, )) + properties.Property("[{{ toUpper .Name}}] Exp, CyclotomicExp and ExpGLV results must be the same in GT", prop.ForAll( + func(a GT, e fp.Element) bool { + a = FinalExponentiation(&a) + + var _e, ne big.Int + {{if or (eq .Name "bw6-761") (eq .Name "bw6-633")}} + k := new(big.Int).SetUint64(6) + {{else if eq .Name "bls24-315"}} + k := new(big.Int).SetUint64(24) + {{ else }} + k := new(big.Int).SetUint64(12) + {{- end}} + e.Exp(e, k) + e.ToBigIntRegular(&_e) + ne.Neg(&_e) + + var b, c, d GT + b.Exp(a, &ne) + b.Inverse(&b) + c.ExpGLV(a, &ne) + c.Conjugate(&c) + d.CyclotomicExp(a, &_e) + + return b.Equal(&c) && c.Equal(&d) + }, + genA, + genP, + )) + properties.Property("[{{ toUpper .Name}}] Expt(Expt) and Exp(t^2) should output the same result in the cyclotomic subgroup", prop.ForAll( func(a GT) bool { var b, c, d GT @@ -67,7 +98,7 @@ func TestPairing(t *testing.T) { Mul(&a, &b) c.Expt(&a).Expt(&c) - d.Exp(&a, xGen).Exp(&d, xGen) + d.Exp(a, &xGen).Exp(d, &xGen) return c.Equal(&d) }, genA, @@ -94,9 +125,9 @@ func TestPairing(t *testing.T) { resa, _ = Pair([]G1Affine{ag1}, []G2Affine{g2GenAff}) resb, _ = Pair([]G1Affine{g1GenAff}, []G2Affine{bg2}) - resab.Exp(&res, ab) - resa.Exp(&resa, bbigint) - resb.Exp(&resb, abigint) + resab.Exp(res, &ab) + resa.Exp(resa, &bbigint) + resb.Exp(resb, &abigint) return resab.Equal(&resa) && resab.Equal(&resb) && !res.Equal(&zero) @@ -350,3 +381,44 @@ func BenchmarkMultiPair(b *testing.B) { }) } } + +func BenchmarkExpGT(b *testing.B) { + + var a GT + a.SetRandom() + a = FinalExponentiation(&a) + + var e fp.Element + e.SetRandom() + {{if or (eq .Name "bw6-761") (eq .Name "bw6-633")}} + k := new(big.Int).SetUint64(6) + {{else if eq .Name "bls24-315"}} + k := new(big.Int).SetUint64(24) + {{ else }} + k := new(big.Int).SetUint64(12) + {{- end}} + e.Exp(e, k) + var _e big.Int + e.ToBigIntRegular(&_e) + + b.Run("Naive windowed Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.Exp(a, &_e) + } + }) + + b.Run("2-NAF cyclotomic Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.CyclotomicExp(a, &_e) + } + }) + + b.Run("windowed 2-dim GLV Exp", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + a.ExpGLV(a, &_e) + } + }) +} diff --git a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl index cf790f930e..8fdcabdd00 100644 --- a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl @@ -2,9 +2,18 @@ import ( "math/big" "encoding/binary" "errors" + "sync" + "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/{{.Curve.Name}}/fp" + "github.com/consensys/gnark-crypto/ecc/{{.Curve.Name}}/fr" ) +var bigIntPool = sync.Pool{ + New: func() interface{} { + return new(big.Int) + }, +} + // E12 is a degree two finite field extension of fp6 type E12 struct { C0, C1 E6 @@ -389,22 +398,171 @@ func BatchInvertE12(a []E12) []E12 { return res } -// Exp sets z=x**e and returns it -func (z *E12) Exp(x *E12, e big.Int) *E12 { +// Exp sets z=xᵏ (mod q¹²) and returns it +// uses 2-bits windowed method +func (z *E12) Exp(x E12, k *big.Int) *E12 { + 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) + } + var res E12 + var ops [3]E12 + res.SetOne() + ops[0].Set(&x) + ops[1].Square(&ops[0]) + ops[2].Set(&ops[0]).Mul(&ops[2], &ops[1]) + b := e.Bytes() for i := range b { w := b[i] - mask := byte(0x80) - for j := 7; j >= 0; j-- { - res.Square(&res) - if (w&mask)>>j != 0 { - res.Mul(&res, x) + mask := byte(0xc0) + for j := 0; j < 4; j++ { + res.Square(&res).Square(&res) + c := (w & mask) >> (6 - 2*j) + if c != 0 { + res.Mul(&res, &ops[c-1]) } - mask = mask >> 1 + mask = mask >> 2 } } + z.Set(&res) + + return z +} + +// CyclotomicExp sets z=xᵏ (mod q¹²) and returns it +// uses 2-NAF decomposition +// x must be in the cyclotomic subgroup +// TODO: use a windowed method +func (z *E12) CyclotomicExp(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var res, xInv E12 + xInv.InverseUnitary(&x) + res.SetOne() + eNAF := make([]int8, e.BitLen()+3) + n := ecc.NafDecomposition(e, eNAF[:]) + for i := n - 1; i >= 0; i-- { + res.CyclotomicSquare(&res) + if eNAF[i] == 1 { + res.Mul(&res, &x) + } else if eNAF[i] == -1 { + res.Mul(&res, &xInv) + } + } + z.Set(&res) + return z +} + +// ExpGLV sets z=xᵏ (q¹²) and returns it +// uses 2-dimensional GLV with 2-bits windowed method +// x must be in GT +// TODO: use 2-NAF +// TODO: use higher dimensional decomposition +func (z *E12) ExpGLV(x E12, k *big.Int) *E12 { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert (=conjugate) + // if k < 0: xᵏ (mod q¹²) == (x⁻¹)ᵏ (mod q¹²) + x.Conjugate(&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) + } + + var table [15]E12 + var res E12 + var s1, s2 fr.Element + + res.SetOne() + + // table[b3b2b1b0-1] = b3b2*Frobinius(x) + b1b0*x + table[0].Set(&x) + table[3].Frobenius(&x) + + // split the scalar, modifies ±x, Frob(x) accordingly + s := ecc.SplitScalar(e, &glvBasis) + + if s[0].Sign() == -1 { + s[0].Neg(&s[0]) + table[0].InverseUnitary(&table[0]) + } + if s[1].Sign() == -1 { + s[1].Neg(&s[1]) + table[3].InverseUnitary(&table[3]) + } + + // precompute table (2 bits sliding window) + // table[b3b2b1b0-1] = b3b2*Frobenius(x) + b1b0*x if b3b2b1b0 != 0 + table[1].CyclotomicSquare(&table[0]) + table[2].Mul(&table[1], &table[0]) + table[4].Mul(&table[3], &table[0]) + table[5].Mul(&table[3], &table[1]) + table[6].Mul(&table[3], &table[2]) + table[7].CyclotomicSquare(&table[3]) + table[8].Mul(&table[7], &table[0]) + table[9].Mul(&table[7], &table[1]) + table[10].Mul(&table[7], &table[2]) + table[11].Mul(&table[7], &table[3]) + table[12].Mul(&table[11], &table[0]) + table[13].Mul(&table[11], &table[1]) + table[14].Mul(&table[11], &table[2]) + + // bounds on the lattice base vectors guarantee that s1, s2 are len(r)/2 bits long max + s1.SetBigInt(&s[0]).FromMont() + s2.SetBigInt(&s[1]).FromMont() + + // loop starts from len(s1)/2 due to the bounds + for i := len(s1) / 2; i >= 0; i-- { + mask := uint64(3) << 62 + for j := 0; j < 32; j++ { + res.CyclotomicSquare(&res).CyclotomicSquare(&res) + b1 := (s1[i] & mask) >> (62 - 2*j) + b2 := (s2[i] & mask) >> (62 - 2*j) + if b1|b2 != 0 { + s := (b2<<2 | b1) + res.Mul(&res, &table[s-1]) + } + mask = mask >> 2 + } + } + z.Set(&res) return z } diff --git a/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl index 9640e1ab77..11612bc060 100644 --- a/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl @@ -4,7 +4,6 @@ import ( "github.com/consensys/gnark-crypto/ecc/{{.Curve.Name}}/fp" ) - // E2 is a degree two finite field extension of fp.Element type E2 struct { A0, A1 fp.Element @@ -143,7 +142,7 @@ func (z *E2) Conjugate(x *E2) *E2 { return z } -// Halve sets z = z / 2 +// Halve sets z = z / 2 func (z *E2) Halve() { z.A0.Halve() z.A1.Halve() @@ -156,15 +155,32 @@ func (z *E2) Legendre() int { return n.Legendre() } -// Exp sets z=x**e and returns it -func (z *E2) Exp(x E2, exponent *big.Int) *E2 { +// Exp sets z=xᵏ (mod q²) and returns it +func (z *E2) Exp(x E2, k *big.Int) *E2 { + 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 := exponent.Bytes() - for i :=0;i> j)) != 0 { + if (w & (0b10000000 >> j)) != 0 { z.Mul(z, &x) } } @@ -290,4 +306,4 @@ func BatchInvertE2(a []E2) []E2 { return res } -{{ template "base" .}} \ No newline at end of file +{{ template "base" .}} diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl index 608228af8c..e94dc8988a 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl @@ -1,5 +1,6 @@ {{$Name := .Curve.Name}} import ( + "math/big" "testing" "github.com/consensys/gnark-crypto/ecc/{{$Name}}/fp" @@ -180,6 +181,7 @@ func TestE12Ops(t *testing.T) { genA := GenE12() genB := GenE12() + genExp := GenFp() properties.Property("[{{ toUpper $Name }}] sub & add should leave an element invariant", prop.ForAll( func(a, b *E12) bool { @@ -360,12 +362,35 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[{{ toUpper $Name }}] Exp and CyclotomicExp results must be the same in the cyclotomic subgroup", prop.ForAll( + func(a *E12, e fp.Element) bool { + var b, c, d E12 + // put in the cyclo subgroup + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + var _e big.Int + k := new(big.Int).SetUint64(12) + e.Exp(e, k) + e.ToBigIntRegular(&_e) + + c.Exp(*a, &_e) + d.CyclotomicExp(*a, &_e) + + return c.Equal(&d) + }, + genA, + genExp, + )) + properties.Property("[{{ toUpper $Name }}] Frobenius of x in E12 should be equal to x^q", prop.ForAll( func(a *E12) bool { var b, c E12 q := fp.Modulus() b.Frobenius(a) - c.Exp(a, *q) + c.Exp(*a, q) return c.Equal(&b) }, genA, @@ -376,7 +401,7 @@ func TestE12Ops(t *testing.T) { var b, c E12 q := fp.Modulus() b.FrobeniusSquare(a) - c.Exp(a, *q).Exp(&c, *q) + c.Exp(*a, q).Exp(c, q) return c.Equal(&b) }, genA,