From 1f6fb79b3fe8be0993ec757fefb82ee097aaba5d Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 1 Jan 2022 00:59:53 +0100 Subject: [PATCH 1/7] feat(E12): GT torus-based compression/decompression --- ecc/bls12-377/internal/fptower/e12.go | 32 +++++++++++++++++++ ecc/bls12-377/internal/fptower/e12_test.go | 15 +++++++++ ecc/bls12-378/internal/fptower/e12.go | 32 +++++++++++++++++++ ecc/bls12-378/internal/fptower/e12_test.go | 15 +++++++++ ecc/bls12-381/internal/fptower/e12.go | 32 +++++++++++++++++++ ecc/bls12-381/internal/fptower/e12_test.go | 15 +++++++++ ecc/bn254/internal/fptower/e12.go | 32 +++++++++++++++++++ ecc/bn254/internal/fptower/e12_test.go | 15 +++++++++ .../template/fq12over6over2/fq12.go.tmpl | 32 +++++++++++++++++++ .../fq12over6over2/tests/fq12.go.tmpl | 15 +++++++++ 10 files changed, 235 insertions(+) diff --git a/ecc/bls12-377/internal/fptower/e12.go b/ecc/bls12-377/internal/fptower/e12.go index cbc2606db..52dc6a2cb 100644 --- a/ecc/bls12-377/internal/fptower/e12.go +++ b/ecc/bls12-377/internal/fptower/e12.go @@ -559,3 +559,35 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } + +// Compress 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 +func (z *E12) Compress() E6 { + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res +} + +// Decompress 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) Decompress() 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 +} diff --git a/ecc/bls12-377/internal/fptower/e12_test.go b/ecc/bls12-377/internal/fptower/e12_test.go index a7cfec865..f94fa516d 100644 --- a/ecc/bls12-377/internal/fptower/e12_test.go +++ b/ecc/bls12-377/internal/fptower/e12_test.go @@ -249,6 +249,21 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-377] 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.Compress() + d := c.Decompress() + return a.Equal(&d) + }, + genA, + )) + properties.Property("[BLS12-377] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 diff --git a/ecc/bls12-378/internal/fptower/e12.go b/ecc/bls12-378/internal/fptower/e12.go index aea14150d..02f44d9f8 100644 --- a/ecc/bls12-378/internal/fptower/e12.go +++ b/ecc/bls12-378/internal/fptower/e12.go @@ -559,3 +559,35 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } + +// Compress 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 +func (z *E12) Compress() E6 { + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res +} + +// Decompress 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) Decompress() 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 +} diff --git a/ecc/bls12-378/internal/fptower/e12_test.go b/ecc/bls12-378/internal/fptower/e12_test.go index 939f945bf..3c4e21456 100644 --- a/ecc/bls12-378/internal/fptower/e12_test.go +++ b/ecc/bls12-378/internal/fptower/e12_test.go @@ -249,6 +249,21 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-378] 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.Compress() + d := c.Decompress() + return a.Equal(&d) + }, + genA, + )) + properties.Property("[BLS12-378] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 diff --git a/ecc/bls12-381/internal/fptower/e12.go b/ecc/bls12-381/internal/fptower/e12.go index bb92cae53..d31caccd2 100644 --- a/ecc/bls12-381/internal/fptower/e12.go +++ b/ecc/bls12-381/internal/fptower/e12.go @@ -559,3 +559,35 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } + +// Compress 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 +func (z *E12) Compress() E6 { + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res +} + +// Decompress 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) Decompress() 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 +} diff --git a/ecc/bls12-381/internal/fptower/e12_test.go b/ecc/bls12-381/internal/fptower/e12_test.go index 6901a716e..272fda004 100644 --- a/ecc/bls12-381/internal/fptower/e12_test.go +++ b/ecc/bls12-381/internal/fptower/e12_test.go @@ -249,6 +249,21 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BLS12-381] 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.Compress() + d := c.Decompress() + return a.Equal(&d) + }, + genA, + )) + properties.Property("[BLS12-381] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 diff --git a/ecc/bn254/internal/fptower/e12.go b/ecc/bn254/internal/fptower/e12.go index 3f8c763fb..74476e836 100644 --- a/ecc/bn254/internal/fptower/e12.go +++ b/ecc/bn254/internal/fptower/e12.go @@ -530,3 +530,35 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } + +// Compress 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 +func (z *E12) Compress() E6 { + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res +} + +// Decompress 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) Decompress() 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 +} diff --git a/ecc/bn254/internal/fptower/e12_test.go b/ecc/bn254/internal/fptower/e12_test.go index 6c56c8680..12c2c4541 100644 --- a/ecc/bn254/internal/fptower/e12_test.go +++ b/ecc/bn254/internal/fptower/e12_test.go @@ -249,6 +249,21 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[BN254] 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.Compress() + d := c.Decompress() + return a.Equal(&d) + }, + genA, + )) + properties.Property("[BN254] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 diff --git a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl index 07fd9fad1..fb580b1ef 100644 --- a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl @@ -538,3 +538,35 @@ func (z *E12) IsInSubGroup() bool { {{define "readFp"}} {{$.To}}.SetBytes(e[{{$.OffSet}}:{{$.OffSet}} + fp.Bytes]) {{end}} + +// Compress 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 +func (z *E12) Compress() E6 { + + var res, tmp, one E6 + one.SetOne() + tmp.Inverse(&z.C1) + res.Add(&z.C0, &one). + Mul(&res, &tmp) + + return res +} + +// Decompress 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) Decompress() 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 +} diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl index 126132c0d..697b21b40 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl @@ -231,6 +231,21 @@ func TestE12Ops(t *testing.T) { genA, )) + properties.Property("[{{ toUpper .Name }}] 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.Compress() + d := c.Decompress() + return a.Equal(&d) + }, + genA, + )) + properties.Property("[{{ toUpper .Name }}] pi**12=id", prop.ForAll( func(a *E12) bool { var b E12 From 5ebda805134b9da5406852f02121cb8b5e0f19f6 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 1 Jan 2022 11:50:52 +0100 Subject: [PATCH 2/7] feat(E12): GT torus-based batch compression/decompression --- ecc/bls12-377/internal/fptower/e12.go | 113 ++++++++++++++++-- ecc/bls12-377/internal/fptower/e12_pairing.go | 2 +- ecc/bls12-377/internal/fptower/e12_test.go | 36 ++++-- ecc/bls12-377/internal/fptower/e2.go | 4 +- ecc/bls12-377/internal/fptower/e2_test.go | 4 +- ecc/bls12-377/internal/fptower/e6.go | 39 ++++++ ecc/bls12-377/internal/fptower/e6_test.go | 14 +++ ecc/bls12-378/internal/fptower/e12.go | 113 ++++++++++++++++-- ecc/bls12-378/internal/fptower/e12_pairing.go | 2 +- ecc/bls12-378/internal/fptower/e12_test.go | 36 ++++-- ecc/bls12-378/internal/fptower/e2.go | 4 +- ecc/bls12-378/internal/fptower/e2_test.go | 4 +- ecc/bls12-378/internal/fptower/e6.go | 39 ++++++ ecc/bls12-378/internal/fptower/e6_test.go | 14 +++ ecc/bls12-381/internal/fptower/e12.go | 113 ++++++++++++++++-- ecc/bls12-381/internal/fptower/e12_pairing.go | 2 +- ecc/bls12-381/internal/fptower/e12_test.go | 36 ++++-- ecc/bls12-381/internal/fptower/e2.go | 4 +- ecc/bls12-381/internal/fptower/e2_test.go | 4 +- ecc/bls12-381/internal/fptower/e6.go | 39 ++++++ ecc/bls12-381/internal/fptower/e6_test.go | 14 +++ ecc/bn254/internal/fptower/e12.go | 113 ++++++++++++++++-- ecc/bn254/internal/fptower/e12_test.go | 36 ++++-- ecc/bn254/internal/fptower/e2.go | 4 +- ecc/bn254/internal/fptower/e2_test.go | 4 +- ecc/bn254/internal/fptower/e6.go | 39 ++++++ ecc/bn254/internal/fptower/e6_test.go | 14 +++ .../template/fq12over6over2/fq12.go.tmpl | 113 ++++++++++++++++-- .../tower/template/fq12over6over2/fq2.go.tmpl | 4 +- .../tower/template/fq12over6over2/fq6.go.tmpl | 39 ++++++ .../fq12over6over2/tests/fq12.go.tmpl | 36 ++++-- .../template/fq12over6over2/tests/fq2.go.tmpl | 4 +- .../template/fq12over6over2/tests/fq6.go.tmpl | 14 +++ 33 files changed, 948 insertions(+), 108 deletions(-) diff --git a/ecc/bls12-377/internal/fptower/e12.go b/ecc/bls12-377/internal/fptower/e12.go index 52dc6a2cb..2a263f86f 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,12 +599,12 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } -// Compress GT/E12 element to half its size +// 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 -func (z *E12) Compress() E6 { +func (z *E12) CompressTorus() E6 { var res, tmp, one E6 one.SetOne() @@ -576,10 +615,37 @@ func (z *E12) Compress() E6 { return res } -// Decompress GT/E12 a compressed element +// 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) Decompress() E12 { +func (z *E6) DecompressTorus() E12 { var res, num, denum E12 num.C0.Set(z) @@ -591,3 +657,32 @@ func (z *E6) Decompress() E12 { 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 +} 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 f94fa516d..66dd9e719 100644 --- a/ecc/bls12-377/internal/fptower/e12_test.go +++ b/ecc/bls12-377/internal/fptower/e12_test.go @@ -249,7 +249,7 @@ func TestE12Ops(t *testing.T) { genA, )) - properties.Property("[BLS12-377] Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + 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) @@ -257,13 +257,33 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.Compress() - d := c.Decompress() + 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 @@ -332,7 +352,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, @@ -354,10 +374,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 d24ab06d2..ef3ea8b04 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 ef16d7a4d..c01d20a2b 100644 --- a/ecc/bls12-377/internal/fptower/e2_test.go +++ b/ecc/bls12-377/internal/fptower/e2_test.go @@ -248,10 +248,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 adc33ceef..34c86530d 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() @@ -262,3 +267,37 @@ 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 +} diff --git a/ecc/bls12-377/internal/fptower/e6_test.go b/ecc/bls12-377/internal/fptower/e6_test.go index fa56bbdae..43656a12e 100644 --- a/ecc/bls12-377/internal/fptower/e6_test.go +++ b/ecc/bls12-377/internal/fptower/e6_test.go @@ -183,6 +183,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-378/internal/fptower/e12.go b/ecc/bls12-378/internal/fptower/e12.go index 02f44d9f8..090e3dc9d 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,12 +599,12 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } -// Compress GT/E12 element to half its size +// 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 -func (z *E12) Compress() E6 { +func (z *E12) CompressTorus() E6 { var res, tmp, one E6 one.SetOne() @@ -576,10 +615,37 @@ func (z *E12) Compress() E6 { return res } -// Decompress GT/E12 a compressed element +// 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) Decompress() E12 { +func (z *E6) DecompressTorus() E12 { var res, num, denum E12 num.C0.Set(z) @@ -591,3 +657,32 @@ func (z *E6) Decompress() E12 { 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 +} 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 3c4e21456..283279dee 100644 --- a/ecc/bls12-378/internal/fptower/e12_test.go +++ b/ecc/bls12-378/internal/fptower/e12_test.go @@ -249,7 +249,7 @@ func TestE12Ops(t *testing.T) { genA, )) - properties.Property("[BLS12-378] Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + 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) @@ -257,13 +257,33 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.Compress() - d := c.Decompress() + 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 @@ -332,7 +352,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, @@ -354,10 +374,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 ff630a714..c6ee3a6df 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 0c3f7e257..ec925b8ae 100644 --- a/ecc/bls12-378/internal/fptower/e2_test.go +++ b/ecc/bls12-378/internal/fptower/e2_test.go @@ -248,10 +248,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 adc33ceef..34c86530d 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() @@ -262,3 +267,37 @@ 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 +} diff --git a/ecc/bls12-378/internal/fptower/e6_test.go b/ecc/bls12-378/internal/fptower/e6_test.go index b6d418d30..0774719dd 100644 --- a/ecc/bls12-378/internal/fptower/e6_test.go +++ b/ecc/bls12-378/internal/fptower/e6_test.go @@ -183,6 +183,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-381/internal/fptower/e12.go b/ecc/bls12-381/internal/fptower/e12.go index d31caccd2..8ae5cc547 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,12 +599,12 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } -// Compress GT/E12 element to half its size +// 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 -func (z *E12) Compress() E6 { +func (z *E12) CompressTorus() E6 { var res, tmp, one E6 one.SetOne() @@ -576,10 +615,37 @@ func (z *E12) Compress() E6 { return res } -// Decompress GT/E12 a compressed element +// 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) Decompress() E12 { +func (z *E6) DecompressTorus() E12 { var res, num, denum E12 num.C0.Set(z) @@ -591,3 +657,32 @@ func (z *E6) Decompress() E12 { 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 +} diff --git a/ecc/bls12-381/internal/fptower/e12_pairing.go b/ecc/bls12-381/internal/fptower/e12_pairing.go index f975b5cba..6553bee81 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]) diff --git a/ecc/bls12-381/internal/fptower/e12_test.go b/ecc/bls12-381/internal/fptower/e12_test.go index 272fda004..9d3ccc21b 100644 --- a/ecc/bls12-381/internal/fptower/e12_test.go +++ b/ecc/bls12-381/internal/fptower/e12_test.go @@ -249,7 +249,7 @@ func TestE12Ops(t *testing.T) { genA, )) - properties.Property("[BLS12-381] Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + 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) @@ -257,13 +257,33 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.Compress() - d := c.Decompress() + 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 @@ -332,7 +352,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, @@ -354,10 +374,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 f108802bd..bb3f37fc5 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 667da416f..51e54710a 100644 --- a/ecc/bls12-381/internal/fptower/e2_test.go +++ b/ecc/bls12-381/internal/fptower/e2_test.go @@ -248,10 +248,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 adc33ceef..34c86530d 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() @@ -262,3 +267,37 @@ 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 +} diff --git a/ecc/bls12-381/internal/fptower/e6_test.go b/ecc/bls12-381/internal/fptower/e6_test.go index a94d3eff4..20d107ec5 100644 --- a/ecc/bls12-381/internal/fptower/e6_test.go +++ b/ecc/bls12-381/internal/fptower/e6_test.go @@ -183,6 +183,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/bn254/internal/fptower/e12.go b/ecc/bn254/internal/fptower/e12.go index 74476e836..e02f40979 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,12 +570,12 @@ func (z *E12) IsInSubGroup() bool { return a.Equal(&b) } -// Compress GT/E12 element to half its size +// 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 -func (z *E12) Compress() E6 { +func (z *E12) CompressTorus() E6 { var res, tmp, one E6 one.SetOne() @@ -547,10 +586,37 @@ func (z *E12) Compress() E6 { return res } -// Decompress GT/E12 a compressed element +// 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) Decompress() E12 { +func (z *E6) DecompressTorus() E12 { var res, num, denum E12 num.C0.Set(z) @@ -562,3 +628,32 @@ func (z *E6) Decompress() E12 { 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 +} diff --git a/ecc/bn254/internal/fptower/e12_test.go b/ecc/bn254/internal/fptower/e12_test.go index 12c2c4541..3f9539981 100644 --- a/ecc/bn254/internal/fptower/e12_test.go +++ b/ecc/bn254/internal/fptower/e12_test.go @@ -249,7 +249,7 @@ func TestE12Ops(t *testing.T) { genA, )) - properties.Property("[BN254] Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + 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) @@ -257,13 +257,33 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.Compress() - d := c.Decompress() + 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 @@ -332,7 +352,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, @@ -354,10 +374,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 9c1b4c7af..d81f06716 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 81b389e1f..c09cb4076 100644 --- a/ecc/bn254/internal/fptower/e2_test.go +++ b/ecc/bn254/internal/fptower/e2_test.go @@ -246,10 +246,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 adc33ceef..34c86530d 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() @@ -262,3 +267,37 @@ 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 +} diff --git a/ecc/bn254/internal/fptower/e6_test.go b/ecc/bn254/internal/fptower/e6_test.go index 0b874fee5..44b2f173e 100644 --- a/ecc/bn254/internal/fptower/e6_test.go +++ b/ecc/bn254/internal/fptower/e6_test.go @@ -183,6 +183,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/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl index fb580b1ef..c0a284c04 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,12 +578,12 @@ func (z *E12) IsInSubGroup() bool { {{$.To}}.SetBytes(e[{{$.OffSet}}:{{$.OffSet}} + fp.Bytes]) {{end}} -// Compress GT/E12 element to half its size +// 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 -func (z *E12) Compress() E6 { +func (z *E12) CompressTorus() E6 { var res, tmp, one E6 one.SetOne() @@ -555,10 +594,37 @@ func (z *E12) Compress() E6 { return res } -// Decompress GT/E12 a compressed element +// 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) Decompress() E12 { +func (z *E6) DecompressTorus() E12 { var res, num, denum E12 num.C0.Set(z) @@ -570,3 +636,32 @@ func (z *E6) Decompress() E12 { 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 +} diff --git a/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq2.go.tmpl index e395d721e..2ec150649 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 843646713..c3c3cb999 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() @@ -244,3 +249,37 @@ 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 +} diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl index 697b21b40..916c1fffd 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl @@ -231,7 +231,7 @@ func TestE12Ops(t *testing.T) { genA, )) - properties.Property("[{{ toUpper .Name }}] Compress/decompress E12 elements in the cyclotomic subgroup", prop.ForAll( + 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) @@ -239,13 +239,33 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.Compress() - d := c.Decompress() + 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 @@ -314,7 +334,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, @@ -336,10 +356,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 aa174990c..6c94064b4 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq2.go.tmpl @@ -228,10 +228,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 955b22fa2..0cca8025b 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq6.go.tmpl @@ -165,6 +165,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 From 503b8fe66134868dfc556af20d3f7e968ff5236e Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 1 Jan 2022 12:14:18 +0100 Subject: [PATCH 3/7] feat(E24): GT torus-based batch compression/decompression --- ecc/bls24-315/internal/fptower/e12.go | 39 +++++ ecc/bls24-315/internal/fptower/e24.go | 137 +++++++++++++++++- ecc/bls24-315/internal/fptower/e24_pairing.go | 2 +- ecc/bls24-315/internal/fptower/e24_test.go | 45 +++++- ecc/bls24-315/internal/fptower/e4.go | 4 +- ecc/bls24-315/internal/fptower/e4_test.go | 4 +- 6 files changed, 216 insertions(+), 15 deletions(-) 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/e24.go b/ecc/bls24-315/internal/fptower/e24.go index c76a52010..721ad7722 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,91 @@ 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 +func (z *E24) CompressTorus() E12 { + + var res, tmp, one E12 + one.SetOne() + tmp.Inverse(&z.D1) + res.Add(&z.D0, &one). + Mul(&res, &tmp) + + return res +} + +// 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..8b4839559 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/e4.go b/ecc/bls24-315/internal/fptower/e4.go index cb4d12dad..03c1741ef 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 diff --git a/ecc/bls24-315/internal/fptower/e4_test.go b/ecc/bls24-315/internal/fptower/e4_test.go index 92f23b0a6..7c9481d2c 100644 --- a/ecc/bls24-315/internal/fptower/e4_test.go +++ b/ecc/bls24-315/internal/fptower/e4_test.go @@ -188,10 +188,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) From 6b743db8c06e8be29807ed595be8ccab6c1cfc0a Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 1 Jan 2022 19:44:06 +0100 Subject: [PATCH 4/7] feat(E6): GT torus-based batch compression/decompression --- ecc/bw6-633/internal/fptower/e3.go | 34 +++++++ ecc/bw6-633/internal/fptower/e3_test.go | 14 +++ ecc/bw6-633/internal/fptower/e6.go | 127 ++++++++++++++++++++++++ ecc/bw6-633/internal/fptower/e6_test.go | 35 +++++++ ecc/bw6-756/internal/fptower/e3.go | 34 +++++++ ecc/bw6-756/internal/fptower/e3_test.go | 14 +++ ecc/bw6-756/internal/fptower/e6.go | 127 ++++++++++++++++++++++++ ecc/bw6-756/internal/fptower/e6_test.go | 35 +++++++ ecc/bw6-761/internal/fptower/e3.go | 34 +++++++ ecc/bw6-761/internal/fptower/e3_test.go | 14 +++ ecc/bw6-761/internal/fptower/e6.go | 127 ++++++++++++++++++++++++ ecc/bw6-761/internal/fptower/e6_test.go | 35 +++++++ 12 files changed, 630 insertions(+) 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 1520dfee9..05db36c67 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..1faa0a95c 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,91 @@ 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 +func (z *E6) CompressTorus() E3 { + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res +} + +// 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..4619b60e5 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-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 87e783576..4b1e65968 100644 --- a/ecc/bw6-756/internal/fptower/e3_test.go +++ b/ecc/bw6-756/internal/fptower/e3_test.go @@ -178,6 +178,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..2290d63e6 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,91 @@ 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 +func (z *E6) CompressTorus() E3 { + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res +} + +// 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..b6b08af1a 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-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 36930fd09..72a9056be 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..408e42f67 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,91 @@ 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 +func (z *E6) CompressTorus() E3 { + + var res, tmp, one E3 + one.SetOne() + tmp.Inverse(&z.B1) + res.Add(&z.B0, &one). + Mul(&res, &tmp) + + return res +} + +// 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..d641d1cf5 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 From a28152b9363f93466a3a408fd721d7e0612d8293 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Sat, 1 Jan 2022 22:26:15 +0100 Subject: [PATCH 5/7] test(all curves): compress/decompress pairing result --- ecc/bls12-377/pairing_test.go | 26 +++++++++++++++++++ ecc/bls12-378/pairing_test.go | 26 +++++++++++++++++++ ecc/bls12-381/pairing_test.go | 26 +++++++++++++++++++ ecc/bls24-315/pairing_test.go | 26 +++++++++++++++++++ ecc/bn254/pairing_test.go | 26 +++++++++++++++++++ ecc/bw6-633/pairing_test.go | 26 +++++++++++++++++++ ecc/bw6-756/pairing_test.go | 26 +++++++++++++++++++ ecc/bw6-761/pairing_test.go | 26 +++++++++++++++++++ .../pairing/template/tests/pairing.go.tmpl | 26 +++++++++++++++++++ 9 files changed, 234 insertions(+) diff --git a/ecc/bls12-377/pairing_test.go b/ecc/bls12-377/pairing_test.go index 96af27ede..1491ce862 100644 --- a/ecc/bls12-377/pairing_test.go +++ b/ecc/bls12-377/pairing_test.go @@ -206,6 +206,32 @@ func TestPairing(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/pairing_test.go b/ecc/bls12-378/pairing_test.go index 5dd1db441..3f26d0061 100644 --- a/ecc/bls12-378/pairing_test.go +++ b/ecc/bls12-378/pairing_test.go @@ -206,6 +206,32 @@ func TestPairing(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/pairing_test.go b/ecc/bls12-381/pairing_test.go index cda5bd3f9..08afcb655 100644 --- a/ecc/bls12-381/pairing_test.go +++ b/ecc/bls12-381/pairing_test.go @@ -206,6 +206,32 @@ func TestPairing(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/pairing_test.go b/ecc/bls24-315/pairing_test.go index e54fca70b..6453767d7 100644 --- a/ecc/bls24-315/pairing_test.go +++ b/ecc/bls24-315/pairing_test.go @@ -207,6 +207,32 @@ func TestPairing(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/bn254/pairing_test.go b/ecc/bn254/pairing_test.go index 8948b1872..a68a64607 100644 --- a/ecc/bn254/pairing_test.go +++ b/ecc/bn254/pairing_test.go @@ -206,6 +206,32 @@ func TestPairing(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/pairing_test.go b/ecc/bw6-633/pairing_test.go index bdffe61d8..a1fff3573 100644 --- a/ecc/bw6-633/pairing_test.go +++ b/ecc/bw6-633/pairing_test.go @@ -207,6 +207,32 @@ func TestPairing(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/pairing_test.go b/ecc/bw6-756/pairing_test.go index 7db065814..5a65ee7c6 100644 --- a/ecc/bw6-756/pairing_test.go +++ b/ecc/bw6-756/pairing_test.go @@ -207,6 +207,32 @@ func TestPairing(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/pairing_test.go b/ecc/bw6-761/pairing_test.go index 2ec8af9ac..1fbec6683 100644 --- a/ecc/bw6-761/pairing_test.go +++ b/ecc/bw6-761/pairing_test.go @@ -207,6 +207,32 @@ func TestPairing(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/pairing/template/tests/pairing.go.tmpl b/internal/generator/pairing/template/tests/pairing.go.tmpl index b4a47335b..8883160ac 100644 --- a/internal/generator/pairing/template/tests/pairing.go.tmpl +++ b/internal/generator/pairing/template/tests/pairing.go.tmpl @@ -199,6 +199,32 @@ func TestPairing(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)) } From 4f9600cf01fafad60e1b9c45f8cde34f73063616 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Wed, 16 Mar 2022 17:45:16 +0100 Subject: [PATCH 6/7] build: fix BatchInvert renaming in SSWU templates --- ecc/bls12-381/sswu_g2.go | 2 +- internal/generator/ecc/template/sswu.go.tmpl | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ecc/bls12-381/sswu_g2.go b/ecc/bls12-381/sswu_g2.go index 5696582b4..b468ce8b0 100644 --- a/ecc/bls12-381/sswu_g2.go +++ b/ecc/bls12-381/sswu_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/internal/generator/ecc/template/sswu.go.tmpl b/internal/generator/ecc/template/sswu.go.tmpl index ba0fcbd92..d261ee921 100644 --- a/internal/generator/ecc/template/sswu.go.tmpl +++ b/internal/generator/ecc/template/sswu.go.tmpl @@ -82,7 +82,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]) @@ -493,4 +499,4 @@ func {{$CurveName}}NotOne(x *{{$CoordType}}) uint64 { //Assuming hash is implemented for G1 and that the curve is over Fp return g1NotOne(&x.{{.FieldCoordName}}0) {{range $i := interval 1 $TowerDegree}} | g1NotZero(&x.{{$.FieldCoordName}}{{$i}}) {{end}} {{end}} -} \ No newline at end of file +} From a3cca237af2182ecc0c5039d9bca4e997ea75264 Mon Sep 17 00:00:00 2001 From: Youssef El Housni Date: Fri, 17 Jun 2022 16:56:07 +0100 Subject: [PATCH 7/7] fix: Torus compression exception case --- ecc/bls12-377/internal/fptower/e12.go | 9 +- ecc/bls12-377/internal/fptower/e12_test.go | 2 +- ecc/bls12-377/pairing_test.go | 2 +- ecc/bls12-378/internal/fptower/e12.go | 9 +- ecc/bls12-378/internal/fptower/e12_test.go | 2 +- ecc/bls12-378/pairing_test.go | 2 +- ecc/bls12-381/internal/fptower/e12.go | 9 +- ecc/bls12-381/internal/fptower/e12_pairing.go | 2 +- ecc/bls12-381/internal/fptower/e12_test.go | 2 +- ecc/bls12-381/pairing_test.go | 2 +- ecc/bls24-315/internal/fptower/e2.go | 10 +- ecc/bls24-315/internal/fptower/e24.go | 9 +- ecc/bls24-315/internal/fptower/e24_test.go | 2 +- ecc/bls24-315/internal/fptower/e2_test.go | 22 +++ ecc/bls24-315/internal/fptower/e4.go | 6 + ecc/bls24-315/pairing_test.go | 2 +- ecc/bls24-317/internal/fptower/e12.go | 39 +++++ ecc/bls24-317/internal/fptower/e2.go | 10 +- ecc/bls24-317/internal/fptower/e24.go | 142 +++++++++++++++++- ecc/bls24-317/internal/fptower/e24_pairing.go | 4 +- ecc/bls24-317/internal/fptower/e24_test.go | 45 +++++- ecc/bls24-317/internal/fptower/e4.go | 4 +- ecc/bls24-317/internal/fptower/e4_test.go | 4 +- ecc/bls24-317/pairing_test.go | 26 ++++ ecc/bn254/internal/fptower/e12.go | 9 +- ecc/bn254/internal/fptower/e12_pairing.go | 2 +- ecc/bn254/internal/fptower/e12_test.go | 2 +- ecc/bn254/pairing_test.go | 2 +- ecc/bw6-633/internal/fptower/e6.go | 9 +- ecc/bw6-633/internal/fptower/e6_test.go | 2 +- ecc/bw6-633/pairing_test.go | 2 +- ecc/bw6-756/internal/fptower/e6.go | 9 +- ecc/bw6-756/internal/fptower/e6_test.go | 2 +- ecc/bw6-756/pairing_test.go | 2 +- ecc/bw6-761/internal/fptower/e6.go | 9 +- ecc/bw6-761/internal/fptower/e6_test.go | 2 +- ecc/bw6-761/pairing_test.go | 2 +- .../pairing/template/tests/pairing.go.tmpl | 2 +- .../template/fq12over6over2/fq12.go.tmpl | 9 +- .../fq12over6over2/tests/fq12.go.tmpl | 2 +- 40 files changed, 368 insertions(+), 65 deletions(-) diff --git a/ecc/bls12-377/internal/fptower/e12.go b/ecc/bls12-377/internal/fptower/e12.go index 9cb2c1095..d04c2c224 100644 --- a/ecc/bls12-377/internal/fptower/e12.go +++ b/ecc/bls12-377/internal/fptower/e12.go @@ -604,7 +604,12 @@ func (z *E12) IsInSubGroup() bool { // 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 -func (z *E12) CompressTorus() E6 { +// 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() @@ -612,7 +617,7 @@ func (z *E12) CompressTorus() E6 { res.Add(&z.C0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E12 elements to half their size diff --git a/ecc/bls12-377/internal/fptower/e12_test.go b/ecc/bls12-377/internal/fptower/e12_test.go index 64a12de29..036a4bdb9 100644 --- a/ecc/bls12-377/internal/fptower/e12_test.go +++ b/ecc/bls12-377/internal/fptower/e12_test.go @@ -260,7 +260,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bls12-377/pairing_test.go b/ecc/bls12-377/pairing_test.go index 53cc0a6cd..55e729f07 100644 --- a/ecc/bls12-377/pairing_test.go +++ b/ecc/bls12-377/pairing_test.go @@ -245,7 +245,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/ecc/bls12-378/internal/fptower/e12.go b/ecc/bls12-378/internal/fptower/e12.go index c790c72fe..bf400bfb1 100644 --- a/ecc/bls12-378/internal/fptower/e12.go +++ b/ecc/bls12-378/internal/fptower/e12.go @@ -604,7 +604,12 @@ func (z *E12) IsInSubGroup() bool { // 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 -func (z *E12) CompressTorus() E6 { +// 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() @@ -612,7 +617,7 @@ func (z *E12) CompressTorus() E6 { res.Add(&z.C0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E12 elements to half their size diff --git a/ecc/bls12-378/internal/fptower/e12_test.go b/ecc/bls12-378/internal/fptower/e12_test.go index 4bee3b021..c854e3515 100644 --- a/ecc/bls12-378/internal/fptower/e12_test.go +++ b/ecc/bls12-378/internal/fptower/e12_test.go @@ -260,7 +260,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bls12-378/pairing_test.go b/ecc/bls12-378/pairing_test.go index 22695ca14..d3f6377ca 100644 --- a/ecc/bls12-378/pairing_test.go +++ b/ecc/bls12-378/pairing_test.go @@ -245,7 +245,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/ecc/bls12-381/internal/fptower/e12.go b/ecc/bls12-381/internal/fptower/e12.go index 058841771..7be45edac 100644 --- a/ecc/bls12-381/internal/fptower/e12.go +++ b/ecc/bls12-381/internal/fptower/e12.go @@ -604,7 +604,12 @@ func (z *E12) IsInSubGroup() bool { // 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 -func (z *E12) CompressTorus() E6 { +// 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() @@ -612,7 +617,7 @@ func (z *E12) CompressTorus() E6 { res.Add(&z.C0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E12 elements to half their size diff --git a/ecc/bls12-381/internal/fptower/e12_pairing.go b/ecc/bls12-381/internal/fptower/e12_pairing.go index 6553bee81..4aaf83074 100644 --- a/ecc/bls12-381/internal/fptower/e12_pairing.go +++ b/ecc/bls12-381/internal/fptower/e12_pairing.go @@ -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 4b51834e7..da6322254 100644 --- a/ecc/bls12-381/internal/fptower/e12_test.go +++ b/ecc/bls12-381/internal/fptower/e12_test.go @@ -260,7 +260,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bls12-381/pairing_test.go b/ecc/bls12-381/pairing_test.go index cfe1a6ebf..15528de76 100644 --- a/ecc/bls12-381/pairing_test.go +++ b/ecc/bls12-381/pairing_test.go @@ -245,7 +245,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) 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 721ad7722..9792420ca 100644 --- a/ecc/bls24-315/internal/fptower/e24.go +++ b/ecc/bls24-315/internal/fptower/e24.go @@ -638,7 +638,12 @@ func (z *E24) IsInSubGroup() bool { // 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 -func (z *E24) CompressTorus() E12 { +// 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() @@ -646,7 +651,7 @@ func (z *E24) CompressTorus() E12 { res.Add(&z.D0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E24 elements to half their size diff --git a/ecc/bls24-315/internal/fptower/e24_test.go b/ecc/bls24-315/internal/fptower/e24_test.go index 8b4839559..0b0dbe888 100644 --- a/ecc/bls24-315/internal/fptower/e24_test.go +++ b/ecc/bls24-315/internal/fptower/e24_test.go @@ -257,7 +257,7 @@ func TestE24Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusQuad(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, 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 03c1741ef..830f98869 100644 --- a/ecc/bls24-315/internal/fptower/e4.go +++ b/ecc/bls24-315/internal/fptower/e4.go @@ -339,3 +339,9 @@ func BatchInvertE4(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/pairing_test.go b/ecc/bls24-315/pairing_test.go index d9af3f1da..38448b0a9 100644 --- a/ecc/bls24-315/pairing_test.go +++ b/ecc/bls24-315/pairing_test.go @@ -246,7 +246,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) 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 de82fe227..10b9ecc1f 100644 --- a/ecc/bn254/internal/fptower/e12.go +++ b/ecc/bn254/internal/fptower/e12.go @@ -575,7 +575,12 @@ func (z *E12) IsInSubGroup() bool { // 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 -func (z *E12) CompressTorus() E6 { +// 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() @@ -583,7 +588,7 @@ func (z *E12) CompressTorus() E6 { res.Add(&z.C0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E12 elements to half their size 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 23c1bed6b..840308fcd 100644 --- a/ecc/bn254/internal/fptower/e12_test.go +++ b/ecc/bn254/internal/fptower/e12_test.go @@ -260,7 +260,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bn254/pairing_test.go b/ecc/bn254/pairing_test.go index 43731eec8..d89f7db8c 100644 --- a/ecc/bn254/pairing_test.go +++ b/ecc/bn254/pairing_test.go @@ -245,7 +245,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/ecc/bw6-633/internal/fptower/e6.go b/ecc/bw6-633/internal/fptower/e6.go index 1faa0a95c..105b97823 100644 --- a/ecc/bw6-633/internal/fptower/e6.go +++ b/ecc/bw6-633/internal/fptower/e6.go @@ -538,7 +538,12 @@ func (z *E6) IsInSubGroup() bool { // 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 -func (z *E6) CompressTorus() E3 { +// 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() @@ -546,7 +551,7 @@ func (z *E6) CompressTorus() E3 { res.Add(&z.B0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E6 elements to half their size diff --git a/ecc/bw6-633/internal/fptower/e6_test.go b/ecc/bw6-633/internal/fptower/e6_test.go index 4619b60e5..faa5d2149 100644 --- a/ecc/bw6-633/internal/fptower/e6_test.go +++ b/ecc/bw6-633/internal/fptower/e6_test.go @@ -237,7 +237,7 @@ func TestE6Ops(t *testing.T) { b.Mul(&b, a) a.Frobenius(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bw6-633/pairing_test.go b/ecc/bw6-633/pairing_test.go index 5bfcd95af..51224c328 100644 --- a/ecc/bw6-633/pairing_test.go +++ b/ecc/bw6-633/pairing_test.go @@ -246,7 +246,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/ecc/bw6-756/internal/fptower/e6.go b/ecc/bw6-756/internal/fptower/e6.go index 2290d63e6..3d172327f 100644 --- a/ecc/bw6-756/internal/fptower/e6.go +++ b/ecc/bw6-756/internal/fptower/e6.go @@ -455,7 +455,12 @@ func (z *E6) IsInSubGroup() bool { // 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 -func (z *E6) CompressTorus() E3 { +// 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() @@ -463,7 +468,7 @@ func (z *E6) CompressTorus() E3 { res.Add(&z.B0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E6 elements to half their size diff --git a/ecc/bw6-756/internal/fptower/e6_test.go b/ecc/bw6-756/internal/fptower/e6_test.go index b6b08af1a..b74c1943b 100644 --- a/ecc/bw6-756/internal/fptower/e6_test.go +++ b/ecc/bw6-756/internal/fptower/e6_test.go @@ -237,7 +237,7 @@ func TestE6Ops(t *testing.T) { b.Mul(&b, a) a.Frobenius(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bw6-756/pairing_test.go b/ecc/bw6-756/pairing_test.go index d80858289..a3d659f3b 100644 --- a/ecc/bw6-756/pairing_test.go +++ b/ecc/bw6-756/pairing_test.go @@ -246,7 +246,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/ecc/bw6-761/internal/fptower/e6.go b/ecc/bw6-761/internal/fptower/e6.go index 408e42f67..b8056ee7a 100644 --- a/ecc/bw6-761/internal/fptower/e6.go +++ b/ecc/bw6-761/internal/fptower/e6.go @@ -508,7 +508,12 @@ func (z *E6) IsInSubGroup() bool { // 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 -func (z *E6) CompressTorus() E3 { +// 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() @@ -516,7 +521,7 @@ func (z *E6) CompressTorus() E3 { res.Add(&z.B0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E6 elements to half their size diff --git a/ecc/bw6-761/internal/fptower/e6_test.go b/ecc/bw6-761/internal/fptower/e6_test.go index d641d1cf5..0c9a66787 100644 --- a/ecc/bw6-761/internal/fptower/e6_test.go +++ b/ecc/bw6-761/internal/fptower/e6_test.go @@ -237,7 +237,7 @@ func TestE6Ops(t *testing.T) { b.Mul(&b, a) a.Frobenius(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) }, diff --git a/ecc/bw6-761/pairing_test.go b/ecc/bw6-761/pairing_test.go index cc0af56c6..bda8d1e21 100644 --- a/ecc/bw6-761/pairing_test.go +++ b/ecc/bw6-761/pairing_test.go @@ -246,7 +246,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/internal/generator/pairing/template/tests/pairing.go.tmpl b/internal/generator/pairing/template/tests/pairing.go.tmpl index 3db193918..0cff77f98 100644 --- a/internal/generator/pairing/template/tests/pairing.go.tmpl +++ b/internal/generator/pairing/template/tests/pairing.go.tmpl @@ -242,7 +242,7 @@ func TestMillerLoop(t *testing.T) { res, _ := Pair([]G1Affine{ag1}, []G2Affine{bg2}) - compressed := res.CompressTorus() + compressed, _ := res.CompressTorus() decompressed := compressed.DecompressTorus() return decompressed.Equal(&res) diff --git a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl index 08dfdf146..cf790f930 100644 --- a/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/fq12.go.tmpl @@ -583,7 +583,12 @@ func (z *E12) IsInSubGroup() bool { // 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 -func (z *E12) CompressTorus() E6 { +// 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() @@ -591,7 +596,7 @@ func (z *E12) CompressTorus() E6 { res.Add(&z.C0, &one). Mul(&res, &tmp) - return res + return res, nil } // BatchCompressTorus GT/E12 elements to half their size diff --git a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl index 96a3d939d..608228af8 100644 --- a/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl +++ b/internal/generator/tower/template/fq12over6over2/tests/fq12.go.tmpl @@ -245,7 +245,7 @@ func TestE12Ops(t *testing.T) { b.Mul(&b, a) a.FrobeniusSquare(&b).Mul(a, &b) - c := a.CompressTorus() + c, _ := a.CompressTorus() d := c.DecompressTorus() return a.Equal(&d) },