diff --git a/ecc/bls12-377/kzg/kzg_test.go b/ecc/bls12-377/kzg/kzg_test.go index 7c6ba6b7e3..a5c5259b47 100644 --- a/ecc/bls12-377/kzg/kzg_test.go +++ b/ecc/bls12-377/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bls12-377/kzg/utils.go b/ecc/bls12-377/kzg/utils.go index a5480f68f6..5e4e6eb4fc 100644 --- a/ecc/bls12-377/kzg/utils.go +++ b/ecc/bls12-377/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bls12-381/kzg/kzg_test.go b/ecc/bls12-381/kzg/kzg_test.go index 839734acdc..7d1176bca7 100644 --- a/ecc/bls12-381/kzg/kzg_test.go +++ b/ecc/bls12-381/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bls12-381/kzg/utils.go b/ecc/bls12-381/kzg/utils.go index 53034f82a4..caec784e6c 100644 --- a/ecc/bls12-381/kzg/utils.go +++ b/ecc/bls12-381/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bls24-315/kzg/kzg_test.go b/ecc/bls24-315/kzg/kzg_test.go index 44eb3c1ce2..75ab4843a1 100644 --- a/ecc/bls24-315/kzg/kzg_test.go +++ b/ecc/bls24-315/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bls24-315/kzg/utils.go b/ecc/bls24-315/kzg/utils.go index f2d516227a..08aa532a7f 100644 --- a/ecc/bls24-315/kzg/utils.go +++ b/ecc/bls24-315/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bls24-317/kzg/kzg_test.go b/ecc/bls24-317/kzg/kzg_test.go index ed82ba06aa..9cb299440a 100644 --- a/ecc/bls24-317/kzg/kzg_test.go +++ b/ecc/bls24-317/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bls24-317/kzg/utils.go b/ecc/bls24-317/kzg/utils.go index 595bb36387..f42fe9e174 100644 --- a/ecc/bls24-317/kzg/utils.go +++ b/ecc/bls24-317/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bn254/kzg/kzg_test.go b/ecc/bn254/kzg/kzg_test.go index 3e505a6326..d0caf216ee 100644 --- a/ecc/bn254/kzg/kzg_test.go +++ b/ecc/bn254/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bn254/kzg/utils.go b/ecc/bn254/kzg/utils.go index c3bab2e0cd..cdd0ca8d4e 100644 --- a/ecc/bn254/kzg/utils.go +++ b/ecc/bn254/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bw6-633/kzg/kzg_test.go b/ecc/bw6-633/kzg/kzg_test.go index d03e5ce338..22d9287d78 100644 --- a/ecc/bw6-633/kzg/kzg_test.go +++ b/ecc/bw6-633/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bw6-633/kzg/utils.go b/ecc/bw6-633/kzg/utils.go index 2b34078ce5..6b15db7d8f 100644 --- a/ecc/bw6-633/kzg/utils.go +++ b/ecc/bw6-633/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/ecc/bw6-761/kzg/kzg_test.go b/ecc/bw6-761/kzg/kzg_test.go index b9f5b159fc..e2176d958a 100644 --- a/ecc/bw6-761/kzg/kzg_test.go +++ b/ecc/bw6-761/kzg/kzg_test.go @@ -119,6 +119,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -727,6 +778,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -824,3 +893,22 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } } + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/ecc/bw6-761/kzg/utils.go b/ecc/bw6-761/kzg/utils.go index e2dedfac79..a3191378fb 100644 --- a/ecc/bw6-761/kzg/utils.go +++ b/ecc/bw6-761/kzg/utils.go @@ -23,54 +23,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) diff --git a/internal/generator/kzg/template/kzg.test.go.tmpl b/internal/generator/kzg/template/kzg.test.go.tmpl index b92f88711e..02fefda8a4 100644 --- a/internal/generator/kzg/template/kzg.test.go.tmpl +++ b/internal/generator/kzg/template/kzg.test.go.tmpl @@ -111,6 +111,57 @@ func TestToLagrangeG1(t *testing.T) { } } +func TestToCanonicalG1(t *testing.T) { + assert := require.New(t) + + const size = 32 + + coeffScalars := sampleG1TransformScalars(size) + expectedCanonical := g1PointsFromScalars(coeffScalars) + + lagrangeScalars := slices.Clone(coeffScalars) + d := fft.NewDomain(uint64(size)) + d.FFTInverse(lagrangeScalars, fft.DIF) + utils.BitReverse(lagrangeScalars) + lagrange := g1PointsFromScalars(lagrangeScalars) + + canonical, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(expectedCanonical[i].Equal(&canonical[i]), "error canonical conversion %d", i) + } +} + +func TestG1TransformRoundTrip(t *testing.T) { + assert := require.New(t) + + const size = 32 + + canonical := g1PointsFromScalars(sampleG1TransformScalars(size)) + lagrange, err := ToLagrangeG1(canonical) + assert.NoError(err) + + roundTrip, err := ToCanonicalG1(lagrange) + assert.NoError(err) + + for i := range size { + assert.True(canonical[i].Equal(&roundTrip[i]), "error G1 transform round-trip %d", i) + } +} + +func TestG1TransformRejectsNonPowerOfTwo(t *testing.T) { + assert := require.New(t) + + points := make([]curve.G1Affine, 3) + + _, err := ToLagrangeG1(points) + assert.Error(err) + + _, err = ToCanonicalG1(points) + assert.Error(err) +} + func TestCommitLagrange(t *testing.T) { // sample a sparse polynomial (here in Lagrange form) size := 64 @@ -719,6 +770,24 @@ func BenchmarkToLagrangeG1(b *testing.B) { } } +func BenchmarkToCanonicalG1(b *testing.B) { + const size = 1 << 14 + + var samplePoints [size]curve.G1Affine + fillBenchBasesG1(samplePoints[:]) + lagrange, err := ToLagrangeG1(samplePoints[:]) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for range b.N { + if _, err := ToCanonicalG1(lagrange); err != nil { + b.Fatal(err) + } + } +} + func BenchmarkSerializeSRS(b *testing.B) { // let's create a quick SRS srs, err := NewSRS(ecc.NextPowerOfTwo(1<<24), big.NewInt(-1)) @@ -815,4 +884,23 @@ func fillBenchBasesG1(samplePoints []curve.G1Affine) { samplePoints[i].X.Add(&samplePoints[i-1].X, &one) samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one) } -} \ No newline at end of file +} + +func sampleG1TransformScalars(size int) []fr.Element { + res := make([]fr.Element, size) + for i := range size { + res[i].SetUint64(uint64(i + 1)) + } + return res +} + +func g1PointsFromScalars(values []fr.Element) []curve.G1Affine { + res := make([]curve.G1Affine, len(values)) + _, _, g1Gen, _ := curve.Generators() + var scalar big.Int + for i := range len(values) { + values[i].BigInt(&scalar) + res[i].ScalarMultiplication(&g1Gen, &scalar) + } + return res +} diff --git a/internal/generator/kzg/template/utils.go.tmpl b/internal/generator/kzg/template/utils.go.tmpl index 614df76ea9..067a054664 100644 --- a/internal/generator/kzg/template/utils.go.tmpl +++ b/internal/generator/kzg/template/utils.go.tmpl @@ -1,8 +1,8 @@ import ( + "fmt" "math/big" "math/bits" "runtime" - "fmt" "github.com/consensys/gnark-crypto/ecc" curve "github.com/consensys/gnark-crypto/ecc/{{ .Name }}" @@ -16,54 +16,68 @@ import ( // fft on the vector consisting of the original SRS. // Size of coeffs must be a power of 2. func ToLagrangeG1(coeffs []curve.G1Affine) ([]curve.G1Affine, error) { - if bits.OnesCount64(uint64(len(coeffs))) != 1 { - return nil, fmt.Errorf("len(coeffs) must be a power of 2") + return transformG1(coeffs, true) +} + +// ToCanonicalG1 in place transform of evals Lagrange form into canonical coeff form. +// It applies the forward fft on the vector of G1 evaluations. +// Size of evals must be a power of 2. +func ToCanonicalG1(evals []curve.G1Affine) ([]curve.G1Affine, error) { + return transformG1(evals, false) +} + +func transformG1(points []curve.G1Affine, inverse bool) ([]curve.G1Affine, error) { + if bits.OnesCount64(uint64(len(points))) != 1 { + return nil, fmt.Errorf("len(points) must be a power of 2") } - size := len(coeffs) + size := len(points) numCPU := uint64(runtime.NumCPU()) maxSplits := bits.TrailingZeros64(ecc.NextPowerOfTwo(numCPU)) << 1 - twiddlesInv, err := computeTwiddlesInv(size) + twiddles, err := computeTwiddles(size, inverse) if err != nil { return nil, err } // batch convert to Jacobian - jCoeffs := make([]curve.G1Jac, len(coeffs)) - for i := range len(coeffs) { - jCoeffs[i].FromAffine(&coeffs[i]) + jPoints := make([]curve.G1Jac, len(points)) + for i := range len(points) { + jPoints[i].FromAffine(&points[i]) } - difFFTG1(jCoeffs, twiddlesInv, 0, maxSplits, nil) + difFFTG1(jPoints, twiddles, 0, maxSplits, nil) // TODO @gbotrel generify the cobra bitreverse function, benchmark it and use it everywhere - bitReverse(jCoeffs) + bitReverse(jPoints) - var invBigint big.Int - var frCardinality fr.Element - frCardinality.SetUint64(uint64(size)) - frCardinality.Inverse(&frCardinality) - frCardinality.BigInt(&invBigint) + if inverse { + var invBigint big.Int + var frCardinality fr.Element + frCardinality.SetUint64(uint64(size)) + frCardinality.Inverse(&frCardinality) + frCardinality.BigInt(&invBigint) - parallel.Execute(size, func(start, end int) { - for i := start; i < end; i++ { - jCoeffs[i].ScalarMultiplication(&jCoeffs[i], &invBigint) - } - }) + parallel.Execute(size, func(start, end int) { + for i := start; i < end; i++ { + jPoints[i].ScalarMultiplication(&jPoints[i], &invBigint) + } + }) + } // batch convert to affine - return curve.BatchJacobianToAffineG1(jCoeffs), nil + return curve.BatchJacobianToAffineG1(jPoints), nil } -func computeTwiddlesInv(cardinality int) ([]*big.Int, error) { +func computeTwiddles(cardinality int, inverse bool) ([]*big.Int, error) { generator, err := fr.Generator(uint64(cardinality)) if err != nil { return nil, err } - // inverse the generator - generator.Inverse(&generator) + if inverse { + generator.Inverse(&generator) + } // nb fft stages nbStages := uint64(bits.TrailingZeros64(uint64(cardinality))) @@ -161,4 +175,4 @@ func difFFTG1(a []curve.G1Jac, twiddles []*big.Int, stage, maxSplits int, chDone difFFTG1(a[0:m], twiddles, nextStage, maxSplits, nil) difFFTG1(a[m:n], twiddles, nextStage, maxSplits, nil) } -} \ No newline at end of file +}