diff --git a/ecc/bls12-377/hash_to_g2.go b/ecc/bls12-377/hash_to_g2.go index cb8bfb964..d60e32370 100644 --- a/ecc/bls12-377/hash_to_g2.go +++ b/ecc/bls12-377/hash_to_g2.go @@ -522,7 +522,7 @@ func g2Isogeny(p *G2Affine) { g2IsogenyYNumerator(&p.Y, &p.X, &p.Y) g2IsogenyXNumerator(&p.X, &p.X) - den = fptower.BatchInvert(den) + den = fptower.BatchInvertE2(den) p.X.Mul(&p.X, &den[0]) p.Y.Mul(&p.Y, &den[1]) diff --git a/ecc/bls12-377/internal/fptower/e12.go b/ecc/bls12-377/internal/fptower/e12.go index f38e0e817..d04c2c224 100644 --- a/ecc/bls12-377/internal/fptower/e12.go +++ b/ecc/bls12-377/internal/fptower/e12.go @@ -105,6 +105,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() +} + // Mul set z=x*y in E12 and return z func (z *E12) Mul(x, y *E12) *E12 { var a, b, c E6 @@ -210,8 +215,8 @@ func (z *E12) CyclotomicSquareCompressed(x *E12) *E12 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E12) Decompress(x *E12) *E12 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E12) DecompressKarabina(x *E12) *E12 { var t [3]E2 var one E2 @@ -256,8 +261,8 @@ func (z *E12) Decompress(x *E12) *E12 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E12) []E12 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E12) []E12 { n := len(x) if n == 0 { @@ -287,7 +292,7 @@ func BatchDecompress(x []E12) []E12 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE2(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -367,6 +372,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 @@ -560,6 +599,99 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } +// CompressTorus GT/E12 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E12) CompressTorus() (E6, error) { + + if z.C1.IsZero() { + return E6{}, errors.New("invalid input") + } + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E12 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E12) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + var one E6 + one.SetOne() + res := make([]E6, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].C1) + } + + t := BatchInvertE6(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].C0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E12 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E6) DecompressTorus() E12 { + + var res, num, denum E12 + num.C0.Set(z) + num.C1.SetOne() + denum.C0.Set(z) + denum.C1.SetOne().Neg(&denum.C1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E12 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E6) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + res := make([]E12, n) + num := make([]E12, n) + denum := make([]E12, n) + + for i := 0; i < n; i++ { + num[i].C0.Set(&x[i]) + num[i].C1.SetOne() + denum[i].C0.Set(&x[i]) + denum[i].C1.SetOne().Neg(&denum[i].C1) + } + + denum = BatchInvertE12(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} + func (z *E12) Select(cond int, caseZ *E12, caseNz *E12) *E12 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-377/internal/fptower/e12_pairing.go b/ecc/bls12-377/internal/fptower/e12_pairing.go index 55c932879..86a918d84 100644 --- a/ecc/bls12-377/internal/fptower/e12_pairing.go +++ b/ecc/bls12-377/internal/fptower/e12_pairing.go @@ -35,7 +35,7 @@ func (z *E12) Expt(x *E12) *E12 { // the remaining 46 bits result.nSquareCompressed(46) - result.Decompress(&result) + result.DecompressKarabina(&result) result.Mul(&result, x) z.Set(&result) diff --git a/ecc/bls12-377/internal/fptower/e12_test.go b/ecc/bls12-377/internal/fptower/e12_test.go index 9ca717a6c..036a4bdb9 100644 --- a/ecc/bls12-377/internal/fptower/e12_test.go +++ b/ecc/bls12-377/internal/fptower/e12_test.go @@ -252,6 +252,41 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-377] Torus-based Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BLS12-377] Torus-based batch Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E12{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-377] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 @@ -308,7 +343,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -330,10 +365,10 @@ func TestE12Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E12{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E12{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bls12-377/internal/fptower/e2.go b/ecc/bls12-377/internal/fptower/e2.go index 8884a757d..ee79f4c9d 100644 --- a/ecc/bls12-377/internal/fptower/e2.go +++ b/ecc/bls12-377/internal/fptower/e2.go @@ -227,9 +227,9 @@ func (z *E2) Sqrt(x *E2) *E2 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE2 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E2) []E2 { +func BatchInvertE2(a []E2) []E2 { res := make([]E2, len(a)) if len(a) == 0 { return res diff --git a/ecc/bls12-377/internal/fptower/e2_test.go b/ecc/bls12-377/internal/fptower/e2_test.go index 9a73c533b..ff750ad33 100644 --- a/ecc/bls12-377/internal/fptower/e2_test.go +++ b/ecc/bls12-377/internal/fptower/e2_test.go @@ -263,10 +263,10 @@ func TestE2Ops(t *testing.T) { genB, )) - properties.Property("[BLS12-377] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BLS12-377] BatchInvertE2 should output the same result as Inverse", prop.ForAll( func(a, b, c *E2) bool { - batch := BatchInvert([]E2{*a, *b, *c}) + batch := BatchInvertE2([]E2{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bls12-377/internal/fptower/e6.go b/ecc/bls12-377/internal/fptower/e6.go index 2ef96c129..2ed48dc26 100644 --- a/ecc/bls12-377/internal/fptower/e6.go +++ b/ecc/bls12-377/internal/fptower/e6.go @@ -63,6 +63,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero() +} + // ToMont converts to Mont form func (z *E6) ToMont() *E6 { z.B0.ToMont() @@ -263,6 +268,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + func (z *E6) Select(cond int, caseZ *E6, caseNz *E6) *E6 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-377/internal/fptower/e6_test.go b/ecc/bls12-377/internal/fptower/e6_test.go index f5d29e392..4b896c62a 100644 --- a/ecc/bls12-377/internal/fptower/e6_test.go +++ b/ecc/bls12-377/internal/fptower/e6_test.go @@ -193,6 +193,20 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-377] BatchInvertE6 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E6) bool { + + batch := BatchInvertE6([]E6{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-377] neg twice should leave an element invariant", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bls12-377/pairing_test.go b/ecc/bls12-377/pairing_test.go index ad740a3e6..55e729f07 100644 --- a/ecc/bls12-377/pairing_test.go +++ b/ecc/bls12-377/pairing_test.go @@ -229,6 +229,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BLS12-377] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls12-378/internal/fptower/e12.go b/ecc/bls12-378/internal/fptower/e12.go index 07c716fbe..bf400bfb1 100644 --- a/ecc/bls12-378/internal/fptower/e12.go +++ b/ecc/bls12-378/internal/fptower/e12.go @@ -105,6 +105,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() +} + // Mul set z=x*y in E12 and return z func (z *E12) Mul(x, y *E12) *E12 { var a, b, c E6 @@ -210,8 +215,8 @@ func (z *E12) CyclotomicSquareCompressed(x *E12) *E12 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E12) Decompress(x *E12) *E12 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E12) DecompressKarabina(x *E12) *E12 { var t [3]E2 var one E2 @@ -256,8 +261,8 @@ func (z *E12) Decompress(x *E12) *E12 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E12) []E12 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E12) []E12 { n := len(x) if n == 0 { @@ -287,7 +292,7 @@ func BatchDecompress(x []E12) []E12 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE2(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -367,6 +372,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 @@ -560,6 +599,99 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } +// CompressTorus GT/E12 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E12) CompressTorus() (E6, error) { + + if z.C1.IsZero() { + return E6{}, errors.New("invalid input") + } + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E12 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E12) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + var one E6 + one.SetOne() + res := make([]E6, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].C1) + } + + t := BatchInvertE6(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].C0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E12 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E6) DecompressTorus() E12 { + + var res, num, denum E12 + num.C0.Set(z) + num.C1.SetOne() + denum.C0.Set(z) + denum.C1.SetOne().Neg(&denum.C1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E12 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E6) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + res := make([]E12, n) + num := make([]E12, n) + denum := make([]E12, n) + + for i := 0; i < n; i++ { + num[i].C0.Set(&x[i]) + num[i].C1.SetOne() + denum[i].C0.Set(&x[i]) + denum[i].C1.SetOne().Neg(&denum[i].C1) + } + + denum = BatchInvertE12(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} + func (z *E12) Select(cond int, caseZ *E12, caseNz *E12) *E12 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-378/internal/fptower/e12_pairing.go b/ecc/bls12-378/internal/fptower/e12_pairing.go index 6441d245f..49d9119ad 100644 --- a/ecc/bls12-378/internal/fptower/e12_pairing.go +++ b/ecc/bls12-378/internal/fptower/e12_pairing.go @@ -63,7 +63,7 @@ func (z *E12) Expt(x *E12) *E12 { // Step 67: result = x^0x9948a20000000000 result.nSquareCompressed(41) - result.Decompress(&result) + result.DecompressKarabina(&result) // Step 68: result = x^0x9948a20000000001 z.Mul(x, &result) diff --git a/ecc/bls12-378/internal/fptower/e12_test.go b/ecc/bls12-378/internal/fptower/e12_test.go index e4afb9e6d..c854e3515 100644 --- a/ecc/bls12-378/internal/fptower/e12_test.go +++ b/ecc/bls12-378/internal/fptower/e12_test.go @@ -252,6 +252,41 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-378] Torus-based Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BLS12-378] Torus-based batch Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E12{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-378] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 @@ -308,7 +343,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -330,10 +365,10 @@ func TestE12Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E12{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E12{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bls12-378/internal/fptower/e2.go b/ecc/bls12-378/internal/fptower/e2.go index 18dc7a9b4..4ca559316 100644 --- a/ecc/bls12-378/internal/fptower/e2.go +++ b/ecc/bls12-378/internal/fptower/e2.go @@ -227,9 +227,9 @@ func (z *E2) Sqrt(x *E2) *E2 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE2 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E2) []E2 { +func BatchInvertE2(a []E2) []E2 { res := make([]E2, len(a)) if len(a) == 0 { return res diff --git a/ecc/bls12-378/internal/fptower/e2_test.go b/ecc/bls12-378/internal/fptower/e2_test.go index a500f4797..327a5e263 100644 --- a/ecc/bls12-378/internal/fptower/e2_test.go +++ b/ecc/bls12-378/internal/fptower/e2_test.go @@ -263,10 +263,10 @@ func TestE2Ops(t *testing.T) { genB, )) - properties.Property("[BLS12-378] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BLS12-378] BatchInvertE2 should output the same result as Inverse", prop.ForAll( func(a, b, c *E2) bool { - batch := BatchInvert([]E2{*a, *b, *c}) + batch := BatchInvertE2([]E2{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bls12-378/internal/fptower/e6.go b/ecc/bls12-378/internal/fptower/e6.go index 2ef96c129..2ed48dc26 100644 --- a/ecc/bls12-378/internal/fptower/e6.go +++ b/ecc/bls12-378/internal/fptower/e6.go @@ -63,6 +63,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero() +} + // ToMont converts to Mont form func (z *E6) ToMont() *E6 { z.B0.ToMont() @@ -263,6 +268,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + func (z *E6) Select(cond int, caseZ *E6, caseNz *E6) *E6 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-378/internal/fptower/e6_test.go b/ecc/bls12-378/internal/fptower/e6_test.go index dbba38d22..a21bb5ad9 100644 --- a/ecc/bls12-378/internal/fptower/e6_test.go +++ b/ecc/bls12-378/internal/fptower/e6_test.go @@ -193,6 +193,20 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-378] BatchInvertE6 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E6) bool { + + batch := BatchInvertE6([]E6{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-378] neg twice should leave an element invariant", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bls12-378/pairing_test.go b/ecc/bls12-378/pairing_test.go index c9f85ea40..d3f6377ca 100644 --- a/ecc/bls12-378/pairing_test.go +++ b/ecc/bls12-378/pairing_test.go @@ -229,6 +229,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BLS12-378] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls12-381/hash_to_g2.go b/ecc/bls12-381/hash_to_g2.go index c8acac513..8f371d417 100644 --- a/ecc/bls12-381/hash_to_g2.go +++ b/ecc/bls12-381/hash_to_g2.go @@ -122,7 +122,7 @@ func g2Isogeny(p *G2Affine) { g2IsogenyYNumerator(&p.Y, &p.X, &p.Y) g2IsogenyXNumerator(&p.X, &p.X) - den = fptower.BatchInvert(den) + den = fptower.BatchInvertE2(den) p.X.Mul(&p.X, &den[0]) p.Y.Mul(&p.Y, &den[1]) diff --git a/ecc/bls12-381/internal/fptower/e12.go b/ecc/bls12-381/internal/fptower/e12.go index 7a425199c..7be45edac 100644 --- a/ecc/bls12-381/internal/fptower/e12.go +++ b/ecc/bls12-381/internal/fptower/e12.go @@ -105,6 +105,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() +} + // Mul set z=x*y in E12 and return z func (z *E12) Mul(x, y *E12) *E12 { var a, b, c E6 @@ -210,8 +215,8 @@ func (z *E12) CyclotomicSquareCompressed(x *E12) *E12 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E12) Decompress(x *E12) *E12 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E12) DecompressKarabina(x *E12) *E12 { var t [3]E2 var one E2 @@ -256,8 +261,8 @@ func (z *E12) Decompress(x *E12) *E12 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E12) []E12 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E12) []E12 { n := len(x) if n == 0 { @@ -287,7 +292,7 @@ func BatchDecompress(x []E12) []E12 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE2(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -367,6 +372,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 @@ -560,6 +599,99 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } +// CompressTorus GT/E12 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E12) CompressTorus() (E6, error) { + + if z.C1.IsZero() { + return E6{}, errors.New("invalid input") + } + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E12 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E12) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + var one E6 + one.SetOne() + res := make([]E6, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].C1) + } + + t := BatchInvertE6(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].C0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E12 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E6) DecompressTorus() E12 { + + var res, num, denum E12 + num.C0.Set(z) + num.C1.SetOne() + denum.C0.Set(z) + denum.C1.SetOne().Neg(&denum.C1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E12 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E6) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + res := make([]E12, n) + num := make([]E12, n) + denum := make([]E12, n) + + for i := 0; i < n; i++ { + num[i].C0.Set(&x[i]) + num[i].C1.SetOne() + denum[i].C0.Set(&x[i]) + denum[i].C1.SetOne().Neg(&denum[i].C1) + } + + denum = BatchInvertE12(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} + func (z *E12) Select(cond int, caseZ *E12, caseNz *E12) *E12 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-381/internal/fptower/e12_pairing.go b/ecc/bls12-381/internal/fptower/e12_pairing.go index f975b5cba..4aaf83074 100644 --- a/ecc/bls12-381/internal/fptower/e12_pairing.go +++ b/ecc/bls12-381/internal/fptower/e12_pairing.go @@ -22,7 +22,7 @@ func (z *E12) ExptHalf(x *E12) *E12 { t[0].Set(&result) result.nSquareCompressed(32) t[1].Set(&result) - batch := BatchDecompress([]E12{t[0], t[1]}) + batch := BatchDecompressKarabina([]E12{t[0], t[1]}) result.Mul(&batch[0], &batch[1]) batch[1].nSquare(9) result.Mul(&result, &batch[1]) @@ -35,7 +35,7 @@ func (z *E12) ExptHalf(x *E12) *E12 { return z.Conjugate(&result) // because tAbsVal is negative } -// Expt set z to x^t in E12 and return z +// Expt set z to xᵗ in E12 and return z // const t uint64 = 15132376222941642752 // negative func (z *E12) Expt(x *E12) *E12 { var result E12 diff --git a/ecc/bls12-381/internal/fptower/e12_test.go b/ecc/bls12-381/internal/fptower/e12_test.go index ca349b89b..da6322254 100644 --- a/ecc/bls12-381/internal/fptower/e12_test.go +++ b/ecc/bls12-381/internal/fptower/e12_test.go @@ -252,6 +252,41 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-381] Torus-based Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BLS12-381] Torus-based batch Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E12{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-381] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 @@ -308,7 +343,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -330,10 +365,10 @@ func TestE12Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E12{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E12{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bls12-381/internal/fptower/e2.go b/ecc/bls12-381/internal/fptower/e2.go index 37e5e691e..20d6479a4 100644 --- a/ecc/bls12-381/internal/fptower/e2.go +++ b/ecc/bls12-381/internal/fptower/e2.go @@ -228,9 +228,9 @@ func (z *E2) Sqrt(x *E2) *E2 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE2 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E2) []E2 { +func BatchInvertE2(a []E2) []E2 { res := make([]E2, len(a)) if len(a) == 0 { return res diff --git a/ecc/bls12-381/internal/fptower/e2_test.go b/ecc/bls12-381/internal/fptower/e2_test.go index f316f8e4a..8cf9a5ed1 100644 --- a/ecc/bls12-381/internal/fptower/e2_test.go +++ b/ecc/bls12-381/internal/fptower/e2_test.go @@ -263,10 +263,10 @@ func TestE2Ops(t *testing.T) { genB, )) - properties.Property("[BLS12-381] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BLS12-381] BatchInvertE2 should output the same result as Inverse", prop.ForAll( func(a, b, c *E2) bool { - batch := BatchInvert([]E2{*a, *b, *c}) + batch := BatchInvertE2([]E2{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bls12-381/internal/fptower/e6.go b/ecc/bls12-381/internal/fptower/e6.go index 2ef96c129..2ed48dc26 100644 --- a/ecc/bls12-381/internal/fptower/e6.go +++ b/ecc/bls12-381/internal/fptower/e6.go @@ -63,6 +63,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero() +} + // ToMont converts to Mont form func (z *E6) ToMont() *E6 { z.B0.ToMont() @@ -263,6 +268,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + func (z *E6) Select(cond int, caseZ *E6, caseNz *E6) *E6 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bls12-381/internal/fptower/e6_test.go b/ecc/bls12-381/internal/fptower/e6_test.go index 6ee89fd55..4b6d1e7e7 100644 --- a/ecc/bls12-381/internal/fptower/e6_test.go +++ b/ecc/bls12-381/internal/fptower/e6_test.go @@ -193,6 +193,20 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-381] BatchInvertE6 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E6) bool { + + batch := BatchInvertE6([]E6{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS12-381] neg twice should leave an element invariant", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bls12-381/pairing_test.go b/ecc/bls12-381/pairing_test.go index 60b760820..15528de76 100644 --- a/ecc/bls12-381/pairing_test.go +++ b/ecc/bls12-381/pairing_test.go @@ -229,6 +229,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BLS12-381] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls24-315/internal/fptower/e12.go b/ecc/bls24-315/internal/fptower/e12.go index ed0b22ec7..535122131 100644 --- a/ecc/bls24-315/internal/fptower/e12.go +++ b/ecc/bls24-315/internal/fptower/e12.go @@ -70,6 +70,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() && z.C2.IsZero() +} + // ToMont converts to Mont form func (z *E12) ToMont() *E12 { z.C0.ToMont() @@ -201,6 +206,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 diff --git a/ecc/bls24-315/internal/fptower/e2.go b/ecc/bls24-315/internal/fptower/e2.go index 21bec62fc..e026fee0e 100644 --- a/ecc/bls24-315/internal/fptower/e2.go +++ b/ecc/bls24-315/internal/fptower/e2.go @@ -25,7 +25,7 @@ type E2 struct { A0, A1 fp.Element } -// Equal returns true if z equals x, fasle otherwise +// Equal returns true if z equals x, false otherwise func (z *E2) Equal(x *E2) bool { return z.A0.Equal(&x.A0) && z.A1.Equal(&x.A1) } @@ -92,7 +92,7 @@ func (z *E2) SetRandom() (*E2, error) { return z, nil } -// IsZero returns true if the two elements are equal, fasle otherwise +// IsZero returns true if the two elements are equal, false otherwise func (z *E2) IsZero() bool { return z.A0.IsZero() && z.A1.IsZero() } @@ -219,3 +219,9 @@ func (z *E2) Sqrt(x *E2) *E2 { return z } + +func (z *E2) Div(x *E2, y *E2) *E2 { + var r E2 + r.Inverse(y).Mul(x, &r) + return z.Set(&r) +} diff --git a/ecc/bls24-315/internal/fptower/e24.go b/ecc/bls24-315/internal/fptower/e24.go index c76a52010..9792420ca 100644 --- a/ecc/bls24-315/internal/fptower/e24.go +++ b/ecc/bls24-315/internal/fptower/e24.go @@ -103,6 +103,11 @@ func (z *E24) SetRandom() (*E24, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E24) IsZero() bool { + return z.D0.IsZero() && z.D1.IsZero() +} + // Mul set z=x*y in E24 and return z func (z *E24) Mul(x, y *E24) *E24 { var a, b, c E12 @@ -208,8 +213,8 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E24) Decompress(x *E24) *E24 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E24) DecompressKarabina(x *E24) *E24 { var t [3]E4 var one E4 @@ -254,8 +259,8 @@ func (z *E24) Decompress(x *E24) *E24 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E24) []E24 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E24) []E24 { n := len(x) if n == 0 { @@ -285,7 +290,7 @@ func BatchDecompress(x []E24) []E24 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE4(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -365,6 +370,40 @@ func (z *E24) Inverse(x *E24) *E24 { return z } +// BatchInvertE24 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE24(a []E24) []E24 { + res := make([]E24, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E24 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E24) Exp(x *E24, e big.Int) *E24 { var res E24 @@ -593,3 +632,96 @@ func (z *E24) IsInSubGroup() bool { return a.Equal(&b) } + +// CompressTorus GT/E24 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E24) CompressTorus() (E12, error) { + + if z.D1.IsZero() { + return E12{}, errors.New("invalid input") + } + + var res, tmp, one E12 + one.SetOne() + tmp.Inverse(&z.D1) + res.Add(&z.D0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E24 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E24) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + var one E12 + one.SetOne() + res := make([]E12, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].D1) + } + + t := BatchInvertE12(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].D0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E24 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E12) DecompressTorus() E24 { + + var res, num, denum E24 + num.D0.Set(z) + num.D1.SetOne() + denum.D0.Set(z) + denum.D1.SetOne().Neg(&denum.D1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E24 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E12) ([]E24, error) { + + n := len(x) + if n == 0 { + return []E24{}, errors.New("invalid input size") + } + + res := make([]E24, n) + num := make([]E24, n) + denum := make([]E24, n) + + for i := 0; i < n; i++ { + num[i].D0.Set(&x[i]) + num[i].D1.SetOne() + denum[i].D0.Set(&x[i]) + denum[i].D1.SetOne().Neg(&denum[i].D1) + } + + denum = BatchInvertE24(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} diff --git a/ecc/bls24-315/internal/fptower/e24_pairing.go b/ecc/bls24-315/internal/fptower/e24_pairing.go index 6aa31ed7b..9f175ca94 100644 --- a/ecc/bls24-315/internal/fptower/e24_pairing.go +++ b/ecc/bls24-315/internal/fptower/e24_pairing.go @@ -20,7 +20,7 @@ func (z *E24) Expt(x *E24) *E24 { result.nSquareCompressed(8) x30.Set(&result) - batch := BatchDecompress([]E24{x20, x22, x30}) + batch := BatchDecompressKarabina([]E24{x20, x22, x30}) x32.CyclotomicSquare(&batch[2]). CyclotomicSquare(&x32). diff --git a/ecc/bls24-315/internal/fptower/e24_test.go b/ecc/bls24-315/internal/fptower/e24_test.go index 32edf7e91..0b0dbe888 100644 --- a/ecc/bls24-315/internal/fptower/e24_test.go +++ b/ecc/bls24-315/internal/fptower/e24_test.go @@ -249,6 +249,41 @@ func TestE24Ops(t *testing.T) { genA, )) + properties.Property("[BLS24-315] Torus-based Compress/decompress E24 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E24) bool { + var b E24 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BLS24-315] Torus-based batch Compress/decompress E24 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E24) bool { + var b E24 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E24{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS24-315] pi**24=id", prop.ForAll( func(a *E24) bool { var b E24 @@ -339,7 +374,7 @@ func TestE24Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusQuad(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -361,10 +396,10 @@ func TestE24Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E24{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E24{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bls24-315/internal/fptower/e2_test.go b/ecc/bls24-315/internal/fptower/e2_test.go index f8b8d4e98..cdadd8b98 100644 --- a/ecc/bls24-315/internal/fptower/e2_test.go +++ b/ecc/bls24-315/internal/fptower/e2_test.go @@ -501,3 +501,25 @@ func BenchmarkE2Conjugate(b *testing.B) { a.Conjugate(&a) } } + +func TestE2Div(t *testing.T) { + + parameters := gopter.DefaultTestParameters() + properties := gopter.NewProperties(parameters) + + genA := GenE2() + genB := GenE2() + + properties.Property("[BLS24-317] dividing then multiplying by the same element does nothing", prop.ForAll( + func(a, b *E2) bool { + var c E2 + c.Div(a, b) + c.Mul(&c, b) + return c.Equal(a) + }, + genA, + genB, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} diff --git a/ecc/bls24-315/internal/fptower/e4.go b/ecc/bls24-315/internal/fptower/e4.go index cb4d12dad..830f98869 100644 --- a/ecc/bls24-315/internal/fptower/e4.go +++ b/ecc/bls24-315/internal/fptower/e4.go @@ -306,9 +306,9 @@ func (z *E4) Sqrt(x *E4) *E4 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE4 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E4) []E4 { +func BatchInvertE4(a []E4) []E4 { res := make([]E4, len(a)) if len(a) == 0 { return res @@ -339,3 +339,9 @@ func BatchInvert(a []E4) []E4 { return res } + +func (z *E4) Div(x *E4, y *E4) *E4 { + var r E4 + r.Inverse(y).Mul(x, &r) + return z.Set(&r) +} diff --git a/ecc/bls24-315/internal/fptower/e4_test.go b/ecc/bls24-315/internal/fptower/e4_test.go index 4bdb13ee0..d84267b2c 100644 --- a/ecc/bls24-315/internal/fptower/e4_test.go +++ b/ecc/bls24-315/internal/fptower/e4_test.go @@ -190,10 +190,10 @@ func TestE4Ops(t *testing.T) { genB, )) - properties.Property("[BLS24-315] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BLS24-315] BatchInvertE4 should output the same result as Inverse", prop.ForAll( func(a, b, c *E4) bool { - batch := BatchInvert([]E4{*a, *b, *c}) + batch := BatchInvertE4([]E4{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bls24-315/pairing_test.go b/ecc/bls24-315/pairing_test.go index 86f6f472d..38448b0a9 100644 --- a/ecc/bls24-315/pairing_test.go +++ b/ecc/bls24-315/pairing_test.go @@ -230,6 +230,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BLS24-315] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bls24-317/internal/fptower/e12.go b/ecc/bls24-317/internal/fptower/e12.go index d1a22f4d5..315432f75 100644 --- a/ecc/bls24-317/internal/fptower/e12.go +++ b/ecc/bls24-317/internal/fptower/e12.go @@ -70,6 +70,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() && z.C2.IsZero() +} + // ToMont converts to Mont form func (z *E12) ToMont() *E12 { z.C0.ToMont() @@ -201,6 +206,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 diff --git a/ecc/bls24-317/internal/fptower/e2.go b/ecc/bls24-317/internal/fptower/e2.go index 1924f21ff..25d035ea8 100644 --- a/ecc/bls24-317/internal/fptower/e2.go +++ b/ecc/bls24-317/internal/fptower/e2.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Code generated by consensys/gnark-crypto DO NOT EDIT - package fptower import ( @@ -124,7 +122,7 @@ func (z *E2) Neg(x *E2) *E2 { // String implements Stringer interface for fancy printing func (z *E2) String() string { - return z.A0.String() + "+" + z.A1.String() + "*u" + return (z.A0.String() + "+" + z.A1.String() + "*u") } // ToMont converts to mont form @@ -157,12 +155,6 @@ func (z *E2) Conjugate(x *E2) *E2 { return z } -// Halve sets z = z / 2 -func (z *E2) Halve() { - z.A0.Halve() - z.A1.Halve() -} - // Legendre returns the Legendre symbol of z func (z *E2) Legendre() int { var n fp.Element diff --git a/ecc/bls24-317/internal/fptower/e24.go b/ecc/bls24-317/internal/fptower/e24.go index c76a52010..9792420ca 100644 --- a/ecc/bls24-317/internal/fptower/e24.go +++ b/ecc/bls24-317/internal/fptower/e24.go @@ -103,6 +103,11 @@ func (z *E24) SetRandom() (*E24, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E24) IsZero() bool { + return z.D0.IsZero() && z.D1.IsZero() +} + // Mul set z=x*y in E24 and return z func (z *E24) Mul(x, y *E24) *E24 { var a, b, c E12 @@ -208,8 +213,8 @@ func (z *E24) CyclotomicSquareCompressed(x *E24) *E24 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E24) Decompress(x *E24) *E24 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E24) DecompressKarabina(x *E24) *E24 { var t [3]E4 var one E4 @@ -254,8 +259,8 @@ func (z *E24) Decompress(x *E24) *E24 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E24) []E24 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E24) []E24 { n := len(x) if n == 0 { @@ -285,7 +290,7 @@ func BatchDecompress(x []E24) []E24 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE4(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -365,6 +370,40 @@ func (z *E24) Inverse(x *E24) *E24 { return z } +// BatchInvertE24 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE24(a []E24) []E24 { + res := make([]E24, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E24 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E24) Exp(x *E24, e big.Int) *E24 { var res E24 @@ -593,3 +632,96 @@ func (z *E24) IsInSubGroup() bool { return a.Equal(&b) } + +// CompressTorus GT/E24 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E24) CompressTorus() (E12, error) { + + if z.D1.IsZero() { + return E12{}, errors.New("invalid input") + } + + var res, tmp, one E12 + one.SetOne() + tmp.Inverse(&z.D1) + res.Add(&z.D0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E24 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E24) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + var one E12 + one.SetOne() + res := make([]E12, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].D1) + } + + t := BatchInvertE12(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].D0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E24 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E12) DecompressTorus() E24 { + + var res, num, denum E24 + num.D0.Set(z) + num.D1.SetOne() + denum.D0.Set(z) + denum.D1.SetOne().Neg(&denum.D1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E24 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E12) ([]E24, error) { + + n := len(x) + if n == 0 { + return []E24{}, errors.New("invalid input size") + } + + res := make([]E24, n) + num := make([]E24, n) + denum := make([]E24, n) + + for i := 0; i < n; i++ { + num[i].D0.Set(&x[i]) + num[i].D1.SetOne() + denum[i].D0.Set(&x[i]) + denum[i].D1.SetOne().Neg(&denum[i].D1) + } + + denum = BatchInvertE24(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} diff --git a/ecc/bls24-317/internal/fptower/e24_pairing.go b/ecc/bls24-317/internal/fptower/e24_pairing.go index d33e20691..20a953107 100644 --- a/ecc/bls24-317/internal/fptower/e24_pairing.go +++ b/ecc/bls24-317/internal/fptower/e24_pairing.go @@ -54,14 +54,14 @@ func (z *E24) Expt(x *E24) *E24 { // Step 19: t0 = x^0x1b200 t0.nSquareCompressed(9) - t0.Decompress(&t0) + t0.DecompressKarabina(&t0) // Step 20: result = x^0x1b203 result.Mul(&result, &t0) // Step 35: result = x^0xd9018000 result.nSquareCompressed(15) - result.Decompress(&result) + result.DecompressKarabina(&result) z.Set(&result) diff --git a/ecc/bls24-317/internal/fptower/e24_test.go b/ecc/bls24-317/internal/fptower/e24_test.go index a2facfb0e..b39bf9064 100644 --- a/ecc/bls24-317/internal/fptower/e24_test.go +++ b/ecc/bls24-317/internal/fptower/e24_test.go @@ -249,6 +249,41 @@ func TestE24Ops(t *testing.T) { genA, )) + properties.Property("[BLS24-315] Torus-based Compress/decompress E24 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E24) bool { + var b E24 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BLS24-315] Torus-based batch Compress/decompress E24 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E24) bool { + var b E24 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusQuad(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E24{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BLS24-317] pi**24=id", prop.ForAll( func(a *E24) bool { var b E24 @@ -339,7 +374,7 @@ func TestE24Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusQuad(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -361,10 +396,10 @@ func TestE24Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E24{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E24{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bls24-317/internal/fptower/e4.go b/ecc/bls24-317/internal/fptower/e4.go index 8a1b65c11..2c84e6d1c 100644 --- a/ecc/bls24-317/internal/fptower/e4.go +++ b/ecc/bls24-317/internal/fptower/e4.go @@ -307,9 +307,9 @@ func (z *E4) Sqrt(x *E4) *E4 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE4 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E4) []E4 { +func BatchInvertE4(a []E4) []E4 { res := make([]E4, len(a)) if len(a) == 0 { return res diff --git a/ecc/bls24-317/internal/fptower/e4_test.go b/ecc/bls24-317/internal/fptower/e4_test.go index 7c41160a1..0afe60267 100644 --- a/ecc/bls24-317/internal/fptower/e4_test.go +++ b/ecc/bls24-317/internal/fptower/e4_test.go @@ -188,10 +188,10 @@ func TestE4Ops(t *testing.T) { genB, )) - properties.Property("[BLS24-317] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BLS24-317] BatchInvertE4 should output the same result as Inverse", prop.ForAll( func(a, b, c *E4) bool { - batch := BatchInvert([]E4{*a, *b, *c}) + batch := BatchInvertE4([]E4{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bls24-317/pairing_test.go b/ecc/bls24-317/pairing_test.go index 692d915ca..0f9a0a75a 100644 --- a/ecc/bls24-317/pairing_test.go +++ b/ecc/bls24-317/pairing_test.go @@ -230,6 +230,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BLS24-317] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bn254/internal/fptower/e12.go b/ecc/bn254/internal/fptower/e12.go index 3aa2bdc8a..10b9ecc1f 100644 --- a/ecc/bn254/internal/fptower/e12.go +++ b/ecc/bn254/internal/fptower/e12.go @@ -105,6 +105,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() +} + // Mul set z=x*y in E12 and return z func (z *E12) Mul(x, y *E12) *E12 { var a, b, c E6 @@ -210,8 +215,8 @@ func (z *E12) CyclotomicSquareCompressed(x *E12) *E12 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E12) Decompress(x *E12) *E12 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E12) DecompressKarabina(x *E12) *E12 { var t [3]E2 var one E2 @@ -256,8 +261,8 @@ func (z *E12) Decompress(x *E12) *E12 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E12) []E12 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E12) []E12 { n := len(x) if n == 0 { @@ -287,7 +292,7 @@ func BatchDecompress(x []E12) []E12 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE2(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -367,6 +372,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 @@ -531,6 +570,99 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } +// CompressTorus GT/E12 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E12) CompressTorus() (E6, error) { + + if z.C1.IsZero() { + return E6{}, errors.New("invalid input") + } + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E12 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E12) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + var one E6 + one.SetOne() + res := make([]E6, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].C1) + } + + t := BatchInvertE6(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].C0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E12 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E6) DecompressTorus() E12 { + + var res, num, denum E12 + num.C0.Set(z) + num.C1.SetOne() + denum.C0.Set(z) + denum.C1.SetOne().Neg(&denum.C1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E12 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E6) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + res := make([]E12, n) + num := make([]E12, n) + denum := make([]E12, n) + + for i := 0; i < n; i++ { + num[i].C0.Set(&x[i]) + num[i].C1.SetOne() + denum[i].C0.Set(&x[i]) + denum[i].C1.SetOne().Neg(&denum[i].C1) + } + + denum = BatchInvertE12(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} + func (z *E12) Select(cond int, caseZ *E12, caseNz *E12) *E12 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bn254/internal/fptower/e12_pairing.go b/ecc/bn254/internal/fptower/e12_pairing.go index b29bc3fef..9b36b6781 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^t in E12 and return z (t is the generator of the curve) +// Expt set z to xᵗ in E12 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 5702f0013..840308fcd 100644 --- a/ecc/bn254/internal/fptower/e12_test.go +++ b/ecc/bn254/internal/fptower/e12_test.go @@ -252,6 +252,41 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BN254] Torus-based Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BN254] Torus-based batch Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E12{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BN254] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 @@ -308,7 +343,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -330,10 +365,10 @@ func TestE12Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E12{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E12{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/ecc/bn254/internal/fptower/e2.go b/ecc/bn254/internal/fptower/e2.go index 8e35d0b26..fe7e11343 100644 --- a/ecc/bn254/internal/fptower/e2.go +++ b/ecc/bn254/internal/fptower/e2.go @@ -228,9 +228,9 @@ func (z *E2) Sqrt(x *E2) *E2 { return z } -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE2 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E2) []E2 { +func BatchInvertE2(a []E2) []E2 { res := make([]E2, len(a)) if len(a) == 0 { return res diff --git a/ecc/bn254/internal/fptower/e2_test.go b/ecc/bn254/internal/fptower/e2_test.go index 15a1dbf55..788d14b59 100644 --- a/ecc/bn254/internal/fptower/e2_test.go +++ b/ecc/bn254/internal/fptower/e2_test.go @@ -261,10 +261,10 @@ func TestE2Ops(t *testing.T) { genB, )) - properties.Property("[BN254] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[BN254] BatchInvertE2 should output the same result as Inverse", prop.ForAll( func(a, b, c *E2) bool { - batch := BatchInvert([]E2{*a, *b, *c}) + batch := BatchInvertE2([]E2{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/ecc/bn254/internal/fptower/e6.go b/ecc/bn254/internal/fptower/e6.go index 2ef96c129..2ed48dc26 100644 --- a/ecc/bn254/internal/fptower/e6.go +++ b/ecc/bn254/internal/fptower/e6.go @@ -63,6 +63,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero() +} + // ToMont converts to Mont form func (z *E6) ToMont() *E6 { z.B0.ToMont() @@ -263,6 +268,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + func (z *E6) Select(cond int, caseZ *E6, caseNz *E6) *E6 { //Might be able to save a nanosecond or two by an aggregate implementation diff --git a/ecc/bn254/internal/fptower/e6_test.go b/ecc/bn254/internal/fptower/e6_test.go index 0030e506a..b47c9ac1d 100644 --- a/ecc/bn254/internal/fptower/e6_test.go +++ b/ecc/bn254/internal/fptower/e6_test.go @@ -193,6 +193,20 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BN254] BatchInvertE6 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E6) bool { + + batch := BatchInvertE6([]E6{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BN254] neg twice should leave an element invariant", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bn254/pairing_test.go b/ecc/bn254/pairing_test.go index f1c064a4e..d89f7db8c 100644 --- a/ecc/bn254/pairing_test.go +++ b/ecc/bn254/pairing_test.go @@ -229,6 +229,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BN254] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-633/internal/fptower/e3.go b/ecc/bw6-633/internal/fptower/e3.go index d523d5798..a0a48a585 100644 --- a/ecc/bw6-633/internal/fptower/e3.go +++ b/ecc/bw6-633/internal/fptower/e3.go @@ -300,3 +300,37 @@ func (z *E3) Inverse(x *E3) *E3 { return z } + +// BatchInvertE3 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE3(a []E3) []E3 { + res := make([]E3, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E3 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} diff --git a/ecc/bw6-633/internal/fptower/e3_test.go b/ecc/bw6-633/internal/fptower/e3_test.go index a25e4e6ba..0526292de 100644 --- a/ecc/bw6-633/internal/fptower/e3_test.go +++ b/ecc/bw6-633/internal/fptower/e3_test.go @@ -178,6 +178,20 @@ func TestE3Ops(t *testing.T) { genA, )) + properties.Property("[BW6-633] BatchInvertE3 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E3) bool { + + batch := BatchInvertE3([]E3{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW6-633] neg twice should leave an element invariant", prop.ForAll( func(a *E3) bool { var b E3 diff --git a/ecc/bw6-633/internal/fptower/e6.go b/ecc/bw6-633/internal/fptower/e6.go index 19e6c860e..105b97823 100644 --- a/ecc/bw6-633/internal/fptower/e6.go +++ b/ecc/bw6-633/internal/fptower/e6.go @@ -105,6 +105,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() +} + // Mul set z=x*y in E6 and return z func (z *E6) Mul(x, y *E6) *E6 { var a, b, c E3 @@ -310,6 +315,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E6) Exp(x *E6, e big.Int) *E6 { var res E6 @@ -493,3 +532,96 @@ func (z *E6) IsInSubGroup() bool { return a.Equal(&b) } + +// CompressTorus GT/E6 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E6) CompressTorus() (E3, error) { + + if z.B1.IsZero() { + return E3{}, errors.New("invalid input") + } + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E6 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E6) ([]E3, error) { + + n := len(x) + if n == 0 { + return []E3{}, errors.New("invalid input size") + } + + var one E3 + one.SetOne() + res := make([]E3, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].B1) + } + + t := BatchInvertE3(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].B0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E6 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E3) DecompressTorus() E6 { + + var res, num, denum E6 + num.B0.Set(z) + num.B1.SetOne() + denum.B0.Set(z) + denum.B1.SetOne().Neg(&denum.B1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E6 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E3) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + res := make([]E6, n) + num := make([]E6, n) + denum := make([]E6, n) + + for i := 0; i < n; i++ { + num[i].B0.Set(&x[i]) + num[i].B1.SetOne() + denum[i].B0.Set(&x[i]) + denum[i].B1.SetOne().Neg(&denum[i].B1) + } + + denum = BatchInvertE6(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} diff --git a/ecc/bw6-633/internal/fptower/e6_test.go b/ecc/bw6-633/internal/fptower/e6_test.go index 49e6cb7bf..faa5d2149 100644 --- a/ecc/bw6-633/internal/fptower/e6_test.go +++ b/ecc/bw6-633/internal/fptower/e6_test.go @@ -229,6 +229,41 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BW6-633] Torus-based Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BW6-633] Torus-based batch Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E6{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW6-633] pi**12=id", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bw6-633/pairing_test.go b/ecc/bw6-633/pairing_test.go index 4b3034aff..51224c328 100644 --- a/ecc/bw6-633/pairing_test.go +++ b/ecc/bw6-633/pairing_test.go @@ -230,6 +230,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BW6-633] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-756/internal/fptower/e3.go b/ecc/bw6-756/internal/fptower/e3.go index 81bf3b878..f41b9da63 100644 --- a/ecc/bw6-756/internal/fptower/e3.go +++ b/ecc/bw6-756/internal/fptower/e3.go @@ -297,3 +297,37 @@ func (z *E3) Inverse(x *E3) *E3 { return z } + +// BatchInvertE3 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE3(a []E3) []E3 { + res := make([]E3, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E3 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} diff --git a/ecc/bw6-756/internal/fptower/e3_test.go b/ecc/bw6-756/internal/fptower/e3_test.go index 047347ec6..2e2eac7fa 100644 --- a/ecc/bw6-756/internal/fptower/e3_test.go +++ b/ecc/bw6-756/internal/fptower/e3_test.go @@ -180,6 +180,20 @@ func TestE3Ops(t *testing.T) { genA, )) + properties.Property("[BW756] BatchInvertE3 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E3) bool { + + batch := BatchInvertE3([]E3{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW756] neg twice should leave an element invariant", prop.ForAll( func(a *E3) bool { var b E3 diff --git a/ecc/bw6-756/internal/fptower/e6.go b/ecc/bw6-756/internal/fptower/e6.go index 7a794fb0c..3d172327f 100644 --- a/ecc/bw6-756/internal/fptower/e6.go +++ b/ecc/bw6-756/internal/fptower/e6.go @@ -105,6 +105,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() +} + // Mul set z=x*y in E6 and return z func (z *E6) Mul(x, y *E6) *E6 { var a, b, c E3 @@ -310,6 +315,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E6) Exp(x *E6, e big.Int) *E6 { var res E6 @@ -410,3 +449,96 @@ func (z *E6) IsInSubGroup() bool { _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 +// 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} +func (z *E6) CompressTorus() (E3, error) { + + if z.B1.IsZero() { + return E3{}, errors.New("invalid input") + } + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E6 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E6) ([]E3, error) { + + n := len(x) + if n == 0 { + return []E3{}, errors.New("invalid input size") + } + + var one E3 + one.SetOne() + res := make([]E3, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].B1) + } + + t := BatchInvertE3(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].B0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E6 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E3) DecompressTorus() E6 { + + var res, num, denum E6 + num.B0.Set(z) + num.B1.SetOne() + denum.B0.Set(z) + denum.B1.SetOne().Neg(&denum.B1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E6 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E3) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + res := make([]E6, n) + num := make([]E6, n) + denum := make([]E6, n) + + for i := 0; i < n; i++ { + num[i].B0.Set(&x[i]) + num[i].B1.SetOne() + denum[i].B0.Set(&x[i]) + denum[i].B1.SetOne().Neg(&denum[i].B1) + } + + denum = BatchInvertE6(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} diff --git a/ecc/bw6-756/internal/fptower/e6_test.go b/ecc/bw6-756/internal/fptower/e6_test.go index 078d5c7b1..b74c1943b 100644 --- a/ecc/bw6-756/internal/fptower/e6_test.go +++ b/ecc/bw6-756/internal/fptower/e6_test.go @@ -229,6 +229,41 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BW6-756] Torus-based Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BW6-756] Torus-based batch Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E6{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW6-756] pi**12=id", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bw6-756/pairing_test.go b/ecc/bw6-756/pairing_test.go index 1ebde67c3..a3d659f3b 100644 --- a/ecc/bw6-756/pairing_test.go +++ b/ecc/bw6-756/pairing_test.go @@ -230,6 +230,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BW6-756] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/ecc/bw6-761/internal/fptower/e3.go b/ecc/bw6-761/internal/fptower/e3.go index 29990fe6b..66010a729 100644 --- a/ecc/bw6-761/internal/fptower/e3.go +++ b/ecc/bw6-761/internal/fptower/e3.go @@ -297,3 +297,37 @@ func (z *E3) Inverse(x *E3) *E3 { return z } + +// BatchInvertE3 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE3(a []E3) []E3 { + res := make([]E3, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E3 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} diff --git a/ecc/bw6-761/internal/fptower/e3_test.go b/ecc/bw6-761/internal/fptower/e3_test.go index 95df7e1cd..1fd50d2f5 100644 --- a/ecc/bw6-761/internal/fptower/e3_test.go +++ b/ecc/bw6-761/internal/fptower/e3_test.go @@ -178,6 +178,20 @@ func TestE3Ops(t *testing.T) { genA, )) + properties.Property("[BW761] BatchInvertE3 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E3) bool { + + batch := BatchInvertE3([]E3{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW761] neg twice should leave an element invariant", prop.ForAll( func(a *E3) bool { var b E3 diff --git a/ecc/bw6-761/internal/fptower/e6.go b/ecc/bw6-761/internal/fptower/e6.go index b8027d9bc..b8056ee7a 100644 --- a/ecc/bw6-761/internal/fptower/e6.go +++ b/ecc/bw6-761/internal/fptower/e6.go @@ -104,6 +104,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() +} + // Mul set z=x*y in E6 and return z func (z *E6) Mul(x, y *E6) *E6 { var a, b, c E3 @@ -309,6 +314,40 @@ func (z *E6) Inverse(x *E6) *E6 { return z } +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E6) Exp(x *E6, e big.Int) *E6 { var res E6 @@ -463,3 +502,96 @@ func (z *E6) IsInSubGroup() bool { return a.Equal(&b) } + +// CompressTorus GT/E6 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E6) CompressTorus() (E3, error) { + + if z.B1.IsZero() { + return E3{}, errors.New("invalid input") + } + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E6 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E6) ([]E3, error) { + + n := len(x) + if n == 0 { + return []E3{}, errors.New("invalid input size") + } + + var one E3 + one.SetOne() + res := make([]E3, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].B1) + } + + t := BatchInvertE3(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].B0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E6 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E3) DecompressTorus() E6 { + + var res, num, denum E6 + num.B0.Set(z) + num.B1.SetOne() + denum.B0.Set(z) + denum.B1.SetOne().Neg(&denum.B1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E6 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E3) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + res := make([]E6, n) + num := make([]E6, n) + denum := make([]E6, n) + + for i := 0; i < n; i++ { + num[i].B0.Set(&x[i]) + num[i].B1.SetOne() + denum[i].B0.Set(&x[i]) + denum[i].B1.SetOne().Neg(&denum[i].B1) + } + + denum = BatchInvertE6(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} diff --git a/ecc/bw6-761/internal/fptower/e6_test.go b/ecc/bw6-761/internal/fptower/e6_test.go index 44aeb6618..0c9a66787 100644 --- a/ecc/bw6-761/internal/fptower/e6_test.go +++ b/ecc/bw6-761/internal/fptower/e6_test.go @@ -229,6 +229,41 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[BW6-761] Torus-based Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[BW6-761] Torus-based batch Compress/decompress E6 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E6) bool { + var b E6 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.Frobenius(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E6{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[BW6-761] pi**12=id", prop.ForAll( func(a *E6) bool { var b E6 diff --git a/ecc/bw6-761/pairing_test.go b/ecc/bw6-761/pairing_test.go index f1def842e..bda8d1e21 100644 --- a/ecc/bw6-761/pairing_test.go +++ b/ecc/bw6-761/pairing_test.go @@ -230,6 +230,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[BW6-761] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/internal/generator/ecc/template/sswu.go.tmpl b/internal/generator/ecc/template/sswu.go.tmpl index 58a451030..f996717a7 100644 --- a/internal/generator/ecc/template/sswu.go.tmpl +++ b/internal/generator/ecc/template/sswu.go.tmpl @@ -77,7 +77,13 @@ func {{$CurveName}}Isogeny(p *{{$AffineType}}) { {{$CurveName}}IsogenyYNumerator(&p.Y, &p.X, &p.Y) {{$CurveName}}IsogenyXNumerator(&p.X, &p.X) - den = {{$package}}.BatchInvert(den) + {{if eq $CoordType "fptower.E2"}} + den = {{$package}}.BatchInvertE2(den) + {{- else if eq $CoordType "fptower.E4"}} + den = {{$package}}.BatchInvertE4(den) + {{- else}} + den = {{$package}}.BatchInvert(den) + {{- end}} p.X.Mul(&p.X, &den[0]) p.Y.Mul(&p.Y, &den[1]) diff --git a/internal/generator/pairing/template/tests/pairing.go.tmpl b/internal/generator/pairing/template/tests/pairing.go.tmpl index 532ecf27a..0cff77f98 100644 --- a/internal/generator/pairing/template/tests/pairing.go.tmpl +++ b/internal/generator/pairing/template/tests/pairing.go.tmpl @@ -226,6 +226,32 @@ func TestMillerLoop(t *testing.T) { genR2, )) + properties.Property("[{{ toUpper .Name}}] compressed pairing", prop.ForAll( + func(a, b fr.Element) bool { + + var ag1 G1Affine + var bg2 G2Affine + + var abigint, bbigint big.Int + + a.ToBigIntRegular(&abigint) + b.ToBigIntRegular(&bbigint) + + ag1.ScalarMultiplication(&g1GenAff, &abigint) + bg2.ScalarMultiplication(&g2GenAff, &bbigint) + + res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) + + compressed, _ := res.CompressTorus() + decompressed := compressed.DecompressTorus() + + return decompressed.Equal(&res) + + }, + genR1, + genR2, + )) + properties.TestingRun(t, gopter.ConsoleReporter(false)) } diff --git a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl index f84e91923..cf790f930 100644 --- a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl @@ -87,6 +87,11 @@ func (z *E12) SetRandom() (*E12, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E12) IsZero() bool { + return z.C0.IsZero() && z.C1.IsZero() +} + // Mul set z=x*y in E12 and return z func (z *E12) Mul(x, y *E12) *E12 { var a, b, c E6 @@ -192,8 +197,8 @@ func (z *E12) CyclotomicSquareCompressed(x *E12) *E12 { return z } -// Decompress Karabina's cyclotomic square result -func (z *E12) Decompress(x *E12) *E12 { +// DecompressKarabina Karabina's cyclotomic square result +func (z *E12) DecompressKarabina(x *E12) *E12 { var t [3]E2 var one E2 @@ -238,8 +243,8 @@ func (z *E12) Decompress(x *E12) *E12 { return z } -// BatchDecompress multiple Karabina's cyclotomic square results -func BatchDecompress(x []E12) []E12 { +// BatchDecompressKarabina multiple Karabina's cyclotomic square results +func BatchDecompressKarabina(x []E12) []E12 { n := len(x) if n == 0 { @@ -269,7 +274,7 @@ func BatchDecompress(x []E12) []E12 { Double(&t1[i]) } - t1 = BatchInvert(t1) // costs 1 inverse + t1 = BatchInvertE2(t1) // costs 1 inverse for i := 0; i < n; i++ { // z4 = g4 @@ -350,6 +355,40 @@ func (z *E12) Inverse(x *E12) *E12 { return z } +// BatchInvertE12 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE12(a []E12) []E12 { + res := make([]E12, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E12 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + // Exp sets z=x**e and returns it func (z *E12) Exp(x *E12, e big.Int) *E12 { var res E12 @@ -539,4 +578,96 @@ func (z *E12) IsInSubGroup() bool { {{$.To}}.SetBytes(e[{{$.OffSet}}:{{$.OffSet}} + fp.Bytes]) {{end}} -{{ template "base" .}} \ No newline at end of file +// CompressTorus GT/E12 element to half its size +// z must be in the cyclotomic subgroup +// i.e. z^(p^4-p^2+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} +func (z *E12) CompressTorus() (E6, error) { + + if z.C1.IsZero() { + return E6{}, errors.New("invalid input") + } + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res, nil +} + +// BatchCompressTorus GT/E12 elements to half their size +// using a batch inversion +func BatchCompressTorus(x []E12) ([]E6, error) { + + n := len(x) + if n == 0 { + return []E6{}, errors.New("invalid input size") + } + + var one E6 + one.SetOne() + res := make([]E6, n) + + for i := 0; i < n; i++ { + res[i].Set(&x[i].C1) + } + + t := BatchInvertE6(res) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Add(&x[i].C0, &one). + Mul(&res[i], &t[i]) + } + + return res, nil +} + +// DecompressTorus GT/E12 a compressed element +// element must be in the cyclotomic subgroup +// "COMPRESSION IN FINITE FIELDS AND TORUS-BASED CRYPTOGRAPHY", K. RUBIN AND A. SILVERBERG +func (z *E6) DecompressTorus() E12 { + + var res, num, denum E12 + num.C0.Set(z) + num.C1.SetOne() + denum.C0.Set(z) + denum.C1.SetOne().Neg(&denum.C1) + res.Inverse(&denum). + Mul(&res, &num) + + return res +} + +// BatchDecompressTorus GT/E12 compressed elements +// using a batch inversion +func BatchDecompressTorus(x []E6) ([]E12, error) { + + n := len(x) + if n == 0 { + return []E12{}, errors.New("invalid input size") + } + + res := make([]E12, n) + num := make([]E12, n) + denum := make([]E12, n) + + for i := 0; i < n; i++ { + num[i].C0.Set(&x[i]) + num[i].C1.SetOne() + denum[i].C0.Set(&x[i]) + denum[i].C1.SetOne().Neg(&denum[i].C1) + } + + denum = BatchInvertE12(denum) // costs 1 inverse + + for i := 0; i < n; i++ { + res[i].Mul(&num[i], &denum[i]) + } + + return res, nil +} +{{ template "base" .}} diff --git a/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl index ea5bdfc69..9640e1ab7 100644 --- a/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl @@ -256,9 +256,9 @@ func (z *E2) Exp(x E2, exponent *big.Int) *E2 { } {{end}} -// BatchInvert returns a new slice with every element inverted. +// BatchInvertE2 returns a new slice with every element inverted. // Uses Montgomery batch inversion trick -func BatchInvert(a []E2) []E2 { +func BatchInvertE2(a []E2) []E2 { res := make([]E2, len(a)) if len(a) == 0 { return res diff --git a/internal/generator/tower/template/fq12over6over2/fq6.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq6.go.tmpl index b4e66e390..dc4c1bca4 100644 --- a/internal/generator/tower/template/fq12over6over2/fq6.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq6.go.tmpl @@ -45,6 +45,11 @@ func (z *E6) SetRandom() (*E6, error) { return z, nil } +// IsZero returns true if the two elements are equal, fasle otherwise +func (z *E6) IsZero() bool { + return z.B0.IsZero() && z.B1.IsZero() && z.B2.IsZero() +} + // ToMont converts to Mont form func (z *E6) ToMont() *E6 { z.B0.ToMont() @@ -245,4 +250,37 @@ func (z *E6) Inverse(x *E6) *E6 { return z } -{{ template "base" .}} \ No newline at end of file +// BatchInvertE6 returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvertE6(a []E6) []E6 { + res := make([]E6, len(a)) + if len(a) == 0 { + return res + } + + zeroes := make([]bool, len(a)) + var accumulator E6 + accumulator.SetOne() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes[i] = true + continue + } + res[i].Set(&accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes[i] { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} +{{ 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 722a78506..608228af8 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl @@ -237,6 +237,41 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[{{ toUpper $Name }}] Torus-based Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + c, _ := a.CompressTorus() + d := c.DecompressTorus() + return a.Equal(&d) + }, + genA, + )) + + properties.Property("[{{ toUpper $Name }}] Torus-based batch Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + func(a, e, f *E12) bool { + var b E12 + b.Conjugate(a) + a.Inverse(a) + b.Mul(&b, a) + a.FrobeniusSquare(&b).Mul(a, &b) + + e.CyclotomicSquare(a) + f.CyclotomicSquare(e) + + c, _ := BatchCompressTorus([]E12{*a, *e, *f}) + d, _ := BatchDecompressTorus(c) + return a.Equal(&d[0]) && e.Equal(&d[1]) && f.Equal(&d[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[{{ toUpper $Name }}] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 @@ -293,7 +328,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) c.Square(a) - d.CyclotomicSquareCompressed(a).Decompress(&d) + d.CyclotomicSquareCompressed(a).DecompressKarabina(&d) return c.Equal(&d) }, genA, @@ -315,10 +350,10 @@ func TestE12Ops(t *testing.T) { a2.nSquareCompressed(2) a4.nSquareCompressed(4) a17.nSquareCompressed(17) - batch := BatchDecompress([]E12{a2, a4, a17}) - a2.Decompress(&a2) - a4.Decompress(&a4) - a17.Decompress(&a17) + batch := BatchDecompressKarabina([]E12{a2, a4, a17}) + a2.DecompressKarabina(&a2) + a4.DecompressKarabina(&a4) + a17.DecompressKarabina(&a17) return a2.Equal(&batch[0]) && a4.Equal(&batch[1]) && a17.Equal(&batch[2]) }, diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl index 65574cb69..eaadf32ef 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl @@ -245,10 +245,10 @@ func TestE2Ops(t *testing.T) { genB, )) - properties.Property("[{{ toUpper $Name }}] BatchInvert should output the same result as Inverse", prop.ForAll( + properties.Property("[{{ toUpper $Name }}] BatchInvertE2 should output the same result as Inverse", prop.ForAll( func(a, b, c *E2) bool { - batch := BatchInvert([]E2{*a, *b, *c}) + batch := BatchInvertE2([]E2{*a, *b, *c}) a.Inverse(a) b.Inverse(b) c.Inverse(c) diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl index 24152cad5..f7f871524 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl @@ -177,6 +177,20 @@ func TestE6Ops(t *testing.T) { genA, )) + properties.Property("[{{ toUpper $Name }}] BatchInvertE6 should output the same result as Inverse", prop.ForAll( + func(a, b, c *E6) bool { + + batch := BatchInvertE6([]E6{*a, *b, *c}) + a.Inverse(a) + b.Inverse(b) + c.Inverse(c) + return a.Equal(&batch[0]) && b.Equal(&batch[1]) && c.Equal(&batch[2]) + }, + genA, + genA, + genA, + )) + properties.Property("[{{ toUpper $Name }}] neg twice should leave an element invariant", prop.ForAll( func(a *E6) bool { var b E6 @@ -311,4 +325,4 @@ _,_= a.SetRandom() } } -{{ template "base" .}} \ No newline at end of file +{{ template "base" .}}