github.com/consensys/gnark-crypto@v0.14.0/ecc/bw6-761/multiexp_test.go (about)

     1  // Copyright 2020 Consensys Software Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Code generated by consensys/gnark-crypto DO NOT EDIT
    16  
    17  package bw6761
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  	"math/bits"
    23  	"math/rand/v2"
    24  	"runtime"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/consensys/gnark-crypto/ecc"
    29  	"github.com/consensys/gnark-crypto/ecc/bw6-761/fr"
    30  	"github.com/leanovate/gopter"
    31  	"github.com/leanovate/gopter/prop"
    32  )
    33  
    34  func TestMultiExpG1(t *testing.T) {
    35  
    36  	parameters := gopter.DefaultTestParameters()
    37  	if testing.Short() {
    38  		parameters.MinSuccessfulTests = 3
    39  	} else {
    40  		parameters.MinSuccessfulTests = nbFuzzShort * 2
    41  	}
    42  
    43  	properties := gopter.NewProperties(parameters)
    44  
    45  	genScalar := GenFr()
    46  
    47  	// size of the multiExps
    48  	const nbSamples = 73
    49  
    50  	// multi exp points
    51  	var samplePoints [nbSamples]G1Affine
    52  	var g G1Jac
    53  	g.Set(&g1Gen)
    54  	for i := 1; i <= nbSamples; i++ {
    55  		samplePoints[i-1].FromJacobian(&g)
    56  		g.AddAssign(&g1Gen)
    57  	}
    58  
    59  	// sprinkle some points at infinity
    60  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
    61  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
    62  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
    63  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
    64  
    65  	// final scalar to use in double and add method (without mixer factor)
    66  	// n(n+1)(2n+1)/6  (sum of the squares from 1 to n)
    67  	var scalar big.Int
    68  	scalar.SetInt64(nbSamples)
    69  	scalar.Mul(&scalar, new(big.Int).SetInt64(nbSamples+1))
    70  	scalar.Mul(&scalar, new(big.Int).SetInt64(2*nbSamples+1))
    71  	scalar.Div(&scalar, new(big.Int).SetInt64(6))
    72  
    73  	// ensure a multiexp that's splitted has the same result as a non-splitted one..
    74  	properties.Property("[G1] Multi exponentiation (cmax) should be consistent with splitted multiexp", prop.ForAll(
    75  		func(mixer fr.Element) bool {
    76  			var samplePointsLarge [nbSamples * 13]G1Affine
    77  			for i := 0; i < 13; i++ {
    78  				copy(samplePointsLarge[i*nbSamples:], samplePoints[:])
    79  			}
    80  
    81  			var rmax, splitted1, splitted2 G1Jac
    82  
    83  			// mixer ensures that all the words of a fpElement are set
    84  			var sampleScalars [nbSamples * 13]fr.Element
    85  
    86  			for i := 1; i <= nbSamples; i++ {
    87  				sampleScalars[i-1].SetUint64(uint64(i)).
    88  					Mul(&sampleScalars[i-1], &mixer)
    89  			}
    90  
    91  			rmax.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{})
    92  			splitted1.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: 128})
    93  			splitted2.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: 51})
    94  			return rmax.Equal(&splitted1) && rmax.Equal(&splitted2)
    95  		},
    96  		genScalar,
    97  	))
    98  
    99  	// cRange is generated from template and contains the available parameters for the multiexp window size
   100  	cRange := []uint64{2, 3, 4, 5, 8, 10, 16}
   101  	if testing.Short() {
   102  		// test only "odd" and "even" (ie windows size divide word size vs not)
   103  		cRange = []uint64{5, 14}
   104  	}
   105  
   106  	properties.Property(fmt.Sprintf("[G1] Multi exponentiation (c in %v) should be consistent with sum of square", cRange), prop.ForAll(
   107  		func(mixer fr.Element) bool {
   108  
   109  			var expected G1Jac
   110  
   111  			// compute expected result with double and add
   112  			var finalScalar, mixerBigInt big.Int
   113  			finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt))
   114  			expected.ScalarMultiplication(&g1Gen, &finalScalar)
   115  
   116  			// mixer ensures that all the words of a fpElement are set
   117  			var sampleScalars [nbSamples]fr.Element
   118  
   119  			for i := 1; i <= nbSamples; i++ {
   120  				sampleScalars[i-1].SetUint64(uint64(i)).
   121  					Mul(&sampleScalars[i-1], &mixer)
   122  			}
   123  
   124  			results := make([]G1Jac, len(cRange))
   125  			for i, c := range cRange {
   126  				_innerMsmG1(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   127  			}
   128  			for i := 1; i < len(results); i++ {
   129  				if !results[i].Equal(&results[i-1]) {
   130  					t.Logf("result for c=%d != c=%d", cRange[i-1], cRange[i])
   131  					return false
   132  				}
   133  			}
   134  			return true
   135  		},
   136  		genScalar,
   137  	))
   138  
   139  	properties.Property(fmt.Sprintf("[G1] Multi exponentiation (c in %v) of points at infinity should output a point at infinity", cRange), prop.ForAll(
   140  		func(mixer fr.Element) bool {
   141  
   142  			var samplePointsZero [nbSamples]G1Affine
   143  
   144  			var expected G1Jac
   145  
   146  			// compute expected result with double and add
   147  			var finalScalar, mixerBigInt big.Int
   148  			finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt))
   149  			expected.ScalarMultiplication(&g1Gen, &finalScalar)
   150  
   151  			// mixer ensures that all the words of a fpElement are set
   152  			var sampleScalars [nbSamples]fr.Element
   153  
   154  			for i := 1; i <= nbSamples; i++ {
   155  				sampleScalars[i-1].SetUint64(uint64(i)).
   156  					Mul(&sampleScalars[i-1], &mixer)
   157  				samplePointsZero[i-1].setInfinity()
   158  			}
   159  
   160  			results := make([]G1Jac, len(cRange))
   161  			for i, c := range cRange {
   162  				_innerMsmG1(&results[i], c, samplePointsZero[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   163  			}
   164  			for i := 0; i < len(results); i++ {
   165  				if !results[i].Z.IsZero() {
   166  					t.Logf("result for c=%d is not infinity", cRange[i])
   167  					return false
   168  				}
   169  			}
   170  			return true
   171  		},
   172  		genScalar,
   173  	))
   174  
   175  	properties.Property(fmt.Sprintf("[G1] Multi exponentiation (c in %v) with a vector of 0s as input should output a point at infinity", cRange), prop.ForAll(
   176  		func(mixer fr.Element) bool {
   177  			// mixer ensures that all the words of a fpElement are set
   178  			var sampleScalars [nbSamples]fr.Element
   179  
   180  			results := make([]G1Jac, len(cRange))
   181  			for i, c := range cRange {
   182  				_innerMsmG1(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   183  			}
   184  			for i := 0; i < len(results); i++ {
   185  				if !results[i].Z.IsZero() {
   186  					t.Logf("result for c=%d is not infinity", cRange[i])
   187  					return false
   188  				}
   189  			}
   190  			return true
   191  		},
   192  		genScalar,
   193  	))
   194  
   195  	// note : this test is here as we expect to have a different multiExp than the above bucket method
   196  	// for small number of points
   197  	properties.Property("[G1] Multi exponentiation (<50points) should be consistent with sum of square", prop.ForAll(
   198  		func(mixer fr.Element) bool {
   199  
   200  			var g G1Jac
   201  			g.Set(&g1Gen)
   202  
   203  			// mixer ensures that all the words of a fpElement are set
   204  			samplePoints := make([]G1Affine, 30)
   205  			sampleScalars := make([]fr.Element, 30)
   206  
   207  			for i := 1; i <= 30; i++ {
   208  				sampleScalars[i-1].SetUint64(uint64(i)).
   209  					Mul(&sampleScalars[i-1], &mixer)
   210  				samplePoints[i-1].FromJacobian(&g)
   211  				g.AddAssign(&g1Gen)
   212  			}
   213  
   214  			var op1MultiExp G1Affine
   215  			op1MultiExp.MultiExp(samplePoints, sampleScalars, ecc.MultiExpConfig{})
   216  
   217  			var finalBigScalar fr.Element
   218  			var finalBigScalarBi big.Int
   219  			var op1ScalarMul G1Affine
   220  			finalBigScalar.SetUint64(9455).Mul(&finalBigScalar, &mixer)
   221  			finalBigScalar.BigInt(&finalBigScalarBi)
   222  			op1ScalarMul.ScalarMultiplication(&g1GenAff, &finalBigScalarBi)
   223  
   224  			return op1ScalarMul.Equal(&op1MultiExp)
   225  		},
   226  		genScalar,
   227  	))
   228  
   229  	properties.TestingRun(t, gopter.ConsoleReporter(false))
   230  }
   231  
   232  func TestCrossMultiExpG1(t *testing.T) {
   233  	const nbSamples = 1 << 14
   234  	// multi exp points
   235  	var samplePoints [nbSamples]G1Affine
   236  	var g G1Jac
   237  	g.Set(&g1Gen)
   238  	for i := 1; i <= nbSamples; i++ {
   239  		samplePoints[i-1].FromJacobian(&g)
   240  		g.AddAssign(&g1Gen)
   241  	}
   242  
   243  	// sprinkle some points at infinity
   244  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   245  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   246  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   247  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   248  
   249  	var sampleScalars [nbSamples]fr.Element
   250  	fillBenchScalars(sampleScalars[:])
   251  
   252  	// sprinkle some doublings
   253  	for i := 10; i < 100; i++ {
   254  		samplePoints[i] = samplePoints[0]
   255  		sampleScalars[i] = sampleScalars[0]
   256  	}
   257  
   258  	// cRange is generated from template and contains the available parameters for the multiexp window size
   259  	cRange := []uint64{2, 3, 4, 5, 8, 10, 16}
   260  	if testing.Short() {
   261  		// test only "odd" and "even" (ie windows size divide word size vs not)
   262  		cRange = []uint64{5, 14}
   263  	}
   264  
   265  	results := make([]G1Jac, len(cRange))
   266  	for i, c := range cRange {
   267  		_innerMsmG1(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   268  	}
   269  
   270  	var r G1Jac
   271  	_innerMsmG1Reference(&r, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   272  
   273  	var expected, got G1Affine
   274  	expected.FromJacobian(&r)
   275  
   276  	for i := 0; i < len(results); i++ {
   277  		got.FromJacobian(&results[i])
   278  		if !expected.Equal(&got) {
   279  			t.Fatalf("cross msm failed with c=%d", cRange[i])
   280  		}
   281  	}
   282  
   283  }
   284  
   285  // _innerMsmG1Reference always do ext jacobian with c == 16
   286  func _innerMsmG1Reference(p *G1Jac, points []G1Affine, scalars []fr.Element, config ecc.MultiExpConfig) *G1Jac {
   287  	// partition the scalars
   288  	digits, _ := partitionScalars(scalars, 16, config.NbTasks)
   289  
   290  	nbChunks := computeNbChunks(16)
   291  
   292  	// for each chunk, spawn one go routine that'll loop through all the scalars in the
   293  	// corresponding bit-window
   294  	// note that buckets is an array allocated on the stack and this is critical for performance
   295  
   296  	// each go routine sends its result in chChunks[i] channel
   297  	chChunks := make([]chan g1JacExtended, nbChunks)
   298  	for i := 0; i < len(chChunks); i++ {
   299  		chChunks[i] = make(chan g1JacExtended, 1)
   300  	}
   301  
   302  	// the last chunk may be processed with a different method than the rest, as it could be smaller.
   303  	n := len(points)
   304  	for j := int(nbChunks - 1); j >= 0; j-- {
   305  		processChunk := processChunkG1Jacobian[bucketg1JacExtendedC16]
   306  		go processChunk(uint64(j), chChunks[j], 16, points, digits[j*n:(j+1)*n], nil)
   307  	}
   308  
   309  	return msmReduceChunkG1Affine(p, int(16), chChunks[:])
   310  }
   311  
   312  func BenchmarkMultiExpG1(b *testing.B) {
   313  
   314  	const (
   315  		pow       = (bits.UintSize / 2) - (bits.UintSize / 8) // 24 on 64 bits arch, 12 on 32 bits
   316  		nbSamples = 1 << pow
   317  	)
   318  
   319  	var (
   320  		samplePoints             [nbSamples]G1Affine
   321  		sampleScalars            [nbSamples]fr.Element
   322  		sampleScalarsSmallValues [nbSamples]fr.Element
   323  		sampleScalarsRedundant   [nbSamples]fr.Element
   324  	)
   325  
   326  	fillBenchScalars(sampleScalars[:])
   327  	copy(sampleScalarsSmallValues[:], sampleScalars[:])
   328  	copy(sampleScalarsRedundant[:], sampleScalars[:])
   329  
   330  	// this means first chunk is going to have more work to do and should be split into several go routines
   331  	for i := 0; i < len(sampleScalarsSmallValues); i++ {
   332  		if i%5 == 0 {
   333  			sampleScalarsSmallValues[i].SetZero()
   334  			sampleScalarsSmallValues[i][0] = 1
   335  		}
   336  	}
   337  
   338  	// bad case for batch affine because scalar distribution might look uniform
   339  	// but over batchSize windows, we may hit a lot of conflicts and force the msm-affine
   340  	// to process small batches of additions to flush its queue of conflicted points.
   341  	for i := 0; i < len(sampleScalarsRedundant); i += 100 {
   342  		for j := i + 1; j < i+100 && j < len(sampleScalarsRedundant); j++ {
   343  			sampleScalarsRedundant[j] = sampleScalarsRedundant[i]
   344  		}
   345  	}
   346  
   347  	fillBenchBasesG1(samplePoints[:])
   348  
   349  	var testPoint G1Affine
   350  
   351  	for i := 5; i <= pow; i++ {
   352  		using := 1 << i
   353  
   354  		b.Run(fmt.Sprintf("%d points", using), func(b *testing.B) {
   355  			b.ResetTimer()
   356  			for j := 0; j < b.N; j++ {
   357  				testPoint.MultiExp(samplePoints[:using], sampleScalars[:using], ecc.MultiExpConfig{})
   358  			}
   359  		})
   360  
   361  		b.Run(fmt.Sprintf("%d points-smallvalues", using), func(b *testing.B) {
   362  			b.ResetTimer()
   363  			for j := 0; j < b.N; j++ {
   364  				testPoint.MultiExp(samplePoints[:using], sampleScalarsSmallValues[:using], ecc.MultiExpConfig{})
   365  			}
   366  		})
   367  
   368  		b.Run(fmt.Sprintf("%d points-redundancy", using), func(b *testing.B) {
   369  			b.ResetTimer()
   370  			for j := 0; j < b.N; j++ {
   371  				testPoint.MultiExp(samplePoints[:using], sampleScalarsRedundant[:using], ecc.MultiExpConfig{})
   372  			}
   373  		})
   374  	}
   375  }
   376  
   377  func BenchmarkMultiExpG1Reference(b *testing.B) {
   378  	const nbSamples = 1 << 20
   379  
   380  	var (
   381  		samplePoints  [nbSamples]G1Affine
   382  		sampleScalars [nbSamples]fr.Element
   383  	)
   384  
   385  	fillBenchScalars(sampleScalars[:])
   386  	fillBenchBasesG1(samplePoints[:])
   387  
   388  	var testPoint G1Affine
   389  
   390  	b.ResetTimer()
   391  	for j := 0; j < b.N; j++ {
   392  		testPoint.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   393  	}
   394  }
   395  
   396  func BenchmarkManyMultiExpG1Reference(b *testing.B) {
   397  	const nbSamples = 1 << 20
   398  
   399  	var (
   400  		samplePoints  [nbSamples]G1Affine
   401  		sampleScalars [nbSamples]fr.Element
   402  	)
   403  
   404  	fillBenchScalars(sampleScalars[:])
   405  	fillBenchBasesG1(samplePoints[:])
   406  
   407  	var t1, t2, t3 G1Affine
   408  	b.ResetTimer()
   409  	for j := 0; j < b.N; j++ {
   410  		var wg sync.WaitGroup
   411  		wg.Add(3)
   412  		go func() {
   413  			t1.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   414  			wg.Done()
   415  		}()
   416  		go func() {
   417  			t2.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   418  			wg.Done()
   419  		}()
   420  		go func() {
   421  			t3.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   422  			wg.Done()
   423  		}()
   424  		wg.Wait()
   425  	}
   426  }
   427  
   428  // WARNING: this return points that are NOT on the curve and is meant to be use for benchmarking
   429  // purposes only. We don't check that the result is valid but just measure "computational complexity".
   430  //
   431  // Rationale for generating points that are not on the curve is that for large benchmarks, generating
   432  // a vector of different points can take minutes. Using the same point or subset will bias the benchmark result
   433  // since bucket additions in extended jacobian coordinates will hit doubling algorithm instead of add.
   434  func fillBenchBasesG1(samplePoints []G1Affine) {
   435  	var r big.Int
   436  	r.SetString("340444420969191673093399857471996460938405", 10)
   437  	samplePoints[0].ScalarMultiplication(&samplePoints[0], &r)
   438  
   439  	one := samplePoints[0].X
   440  	one.SetOne()
   441  
   442  	for i := 1; i < len(samplePoints); i++ {
   443  		samplePoints[i].X.Add(&samplePoints[i-1].X, &one)
   444  		samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one)
   445  	}
   446  }
   447  
   448  func TestMultiExpG2(t *testing.T) {
   449  
   450  	parameters := gopter.DefaultTestParameters()
   451  	if testing.Short() {
   452  		parameters.MinSuccessfulTests = 3
   453  	} else {
   454  		parameters.MinSuccessfulTests = nbFuzzShort * 2
   455  	}
   456  
   457  	properties := gopter.NewProperties(parameters)
   458  
   459  	genScalar := GenFr()
   460  
   461  	// size of the multiExps
   462  	const nbSamples = 73
   463  
   464  	// multi exp points
   465  	var samplePoints [nbSamples]G2Affine
   466  	var g G2Jac
   467  	g.Set(&g2Gen)
   468  	for i := 1; i <= nbSamples; i++ {
   469  		samplePoints[i-1].FromJacobian(&g)
   470  		g.AddAssign(&g2Gen)
   471  	}
   472  
   473  	// sprinkle some points at infinity
   474  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   475  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   476  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   477  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   478  
   479  	// final scalar to use in double and add method (without mixer factor)
   480  	// n(n+1)(2n+1)/6  (sum of the squares from 1 to n)
   481  	var scalar big.Int
   482  	scalar.SetInt64(nbSamples)
   483  	scalar.Mul(&scalar, new(big.Int).SetInt64(nbSamples+1))
   484  	scalar.Mul(&scalar, new(big.Int).SetInt64(2*nbSamples+1))
   485  	scalar.Div(&scalar, new(big.Int).SetInt64(6))
   486  
   487  	// ensure a multiexp that's splitted has the same result as a non-splitted one..
   488  	properties.Property("[G2] Multi exponentiation (cmax) should be consistent with splitted multiexp", prop.ForAll(
   489  		func(mixer fr.Element) bool {
   490  			var samplePointsLarge [nbSamples * 13]G2Affine
   491  			for i := 0; i < 13; i++ {
   492  				copy(samplePointsLarge[i*nbSamples:], samplePoints[:])
   493  			}
   494  
   495  			var rmax, splitted1, splitted2 G2Jac
   496  
   497  			// mixer ensures that all the words of a fpElement are set
   498  			var sampleScalars [nbSamples * 13]fr.Element
   499  
   500  			for i := 1; i <= nbSamples; i++ {
   501  				sampleScalars[i-1].SetUint64(uint64(i)).
   502  					Mul(&sampleScalars[i-1], &mixer)
   503  			}
   504  
   505  			rmax.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{})
   506  			splitted1.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: 128})
   507  			splitted2.MultiExp(samplePointsLarge[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: 51})
   508  			return rmax.Equal(&splitted1) && rmax.Equal(&splitted2)
   509  		},
   510  		genScalar,
   511  	))
   512  
   513  	// cRange is generated from template and contains the available parameters for the multiexp window size
   514  	// for g2, CI suffers with large c size since it needs to allocate a lot of memory for the buckets.
   515  	// test only "odd" and "even" (ie windows size divide word size vs not)
   516  	cRange := []uint64{5, 14}
   517  
   518  	properties.Property(fmt.Sprintf("[G2] Multi exponentiation (c in %v) should be consistent with sum of square", cRange), prop.ForAll(
   519  		func(mixer fr.Element) bool {
   520  
   521  			var expected G2Jac
   522  
   523  			// compute expected result with double and add
   524  			var finalScalar, mixerBigInt big.Int
   525  			finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt))
   526  			expected.ScalarMultiplication(&g2Gen, &finalScalar)
   527  
   528  			// mixer ensures that all the words of a fpElement are set
   529  			var sampleScalars [nbSamples]fr.Element
   530  
   531  			for i := 1; i <= nbSamples; i++ {
   532  				sampleScalars[i-1].SetUint64(uint64(i)).
   533  					Mul(&sampleScalars[i-1], &mixer)
   534  			}
   535  
   536  			results := make([]G2Jac, len(cRange))
   537  			for i, c := range cRange {
   538  				_innerMsmG2(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   539  			}
   540  			for i := 1; i < len(results); i++ {
   541  				if !results[i].Equal(&results[i-1]) {
   542  					t.Logf("result for c=%d != c=%d", cRange[i-1], cRange[i])
   543  					return false
   544  				}
   545  			}
   546  			return true
   547  		},
   548  		genScalar,
   549  	))
   550  
   551  	properties.Property(fmt.Sprintf("[G2] Multi exponentiation (c in %v) of points at infinity should output a point at infinity", cRange), prop.ForAll(
   552  		func(mixer fr.Element) bool {
   553  
   554  			var samplePointsZero [nbSamples]G2Affine
   555  
   556  			var expected G2Jac
   557  
   558  			// compute expected result with double and add
   559  			var finalScalar, mixerBigInt big.Int
   560  			finalScalar.Mul(&scalar, mixer.BigInt(&mixerBigInt))
   561  			expected.ScalarMultiplication(&g2Gen, &finalScalar)
   562  
   563  			// mixer ensures that all the words of a fpElement are set
   564  			var sampleScalars [nbSamples]fr.Element
   565  
   566  			for i := 1; i <= nbSamples; i++ {
   567  				sampleScalars[i-1].SetUint64(uint64(i)).
   568  					Mul(&sampleScalars[i-1], &mixer)
   569  				samplePointsZero[i-1].setInfinity()
   570  			}
   571  
   572  			results := make([]G2Jac, len(cRange))
   573  			for i, c := range cRange {
   574  				_innerMsmG2(&results[i], c, samplePointsZero[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   575  			}
   576  			for i := 0; i < len(results); i++ {
   577  				if !results[i].Z.IsZero() {
   578  					t.Logf("result for c=%d is not infinity", cRange[i])
   579  					return false
   580  				}
   581  			}
   582  			return true
   583  		},
   584  		genScalar,
   585  	))
   586  
   587  	properties.Property(fmt.Sprintf("[G2] Multi exponentiation (c in %v) with a vector of 0s as input should output a point at infinity", cRange), prop.ForAll(
   588  		func(mixer fr.Element) bool {
   589  			// mixer ensures that all the words of a fpElement are set
   590  			var sampleScalars [nbSamples]fr.Element
   591  
   592  			results := make([]G2Jac, len(cRange))
   593  			for i, c := range cRange {
   594  				_innerMsmG2(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   595  			}
   596  			for i := 0; i < len(results); i++ {
   597  				if !results[i].Z.IsZero() {
   598  					t.Logf("result for c=%d is not infinity", cRange[i])
   599  					return false
   600  				}
   601  			}
   602  			return true
   603  		},
   604  		genScalar,
   605  	))
   606  
   607  	// note : this test is here as we expect to have a different multiExp than the above bucket method
   608  	// for small number of points
   609  	properties.Property("[G2] Multi exponentiation (<50points) should be consistent with sum of square", prop.ForAll(
   610  		func(mixer fr.Element) bool {
   611  
   612  			var g G2Jac
   613  			g.Set(&g2Gen)
   614  
   615  			// mixer ensures that all the words of a fpElement are set
   616  			samplePoints := make([]G2Affine, 30)
   617  			sampleScalars := make([]fr.Element, 30)
   618  
   619  			for i := 1; i <= 30; i++ {
   620  				sampleScalars[i-1].SetUint64(uint64(i)).
   621  					Mul(&sampleScalars[i-1], &mixer)
   622  				samplePoints[i-1].FromJacobian(&g)
   623  				g.AddAssign(&g2Gen)
   624  			}
   625  
   626  			var op1MultiExp G2Affine
   627  			op1MultiExp.MultiExp(samplePoints, sampleScalars, ecc.MultiExpConfig{})
   628  
   629  			var finalBigScalar fr.Element
   630  			var finalBigScalarBi big.Int
   631  			var op1ScalarMul G2Affine
   632  			finalBigScalar.SetUint64(9455).Mul(&finalBigScalar, &mixer)
   633  			finalBigScalar.BigInt(&finalBigScalarBi)
   634  			op1ScalarMul.ScalarMultiplication(&g2GenAff, &finalBigScalarBi)
   635  
   636  			return op1ScalarMul.Equal(&op1MultiExp)
   637  		},
   638  		genScalar,
   639  	))
   640  
   641  	properties.TestingRun(t, gopter.ConsoleReporter(false))
   642  }
   643  
   644  func TestCrossMultiExpG2(t *testing.T) {
   645  	const nbSamples = 1 << 14
   646  	// multi exp points
   647  	var samplePoints [nbSamples]G2Affine
   648  	var g G2Jac
   649  	g.Set(&g2Gen)
   650  	for i := 1; i <= nbSamples; i++ {
   651  		samplePoints[i-1].FromJacobian(&g)
   652  		g.AddAssign(&g2Gen)
   653  	}
   654  
   655  	// sprinkle some points at infinity
   656  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   657  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   658  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   659  	samplePoints[rand.N(nbSamples)].setInfinity() //#nosec G404 weak rng is fine here
   660  
   661  	var sampleScalars [nbSamples]fr.Element
   662  	fillBenchScalars(sampleScalars[:])
   663  
   664  	// sprinkle some doublings
   665  	for i := 10; i < 100; i++ {
   666  		samplePoints[i] = samplePoints[0]
   667  		sampleScalars[i] = sampleScalars[0]
   668  	}
   669  
   670  	// cRange is generated from template and contains the available parameters for the multiexp window size
   671  	// for g2, CI suffers with large c size since it needs to allocate a lot of memory for the buckets.
   672  	// test only "odd" and "even" (ie windows size divide word size vs not)
   673  	cRange := []uint64{5, 14}
   674  
   675  	results := make([]G2Jac, len(cRange))
   676  	for i, c := range cRange {
   677  		_innerMsmG2(&results[i], c, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   678  	}
   679  
   680  	var r G2Jac
   681  	_innerMsmG2Reference(&r, samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{NbTasks: runtime.NumCPU()})
   682  
   683  	var expected, got G2Affine
   684  	expected.FromJacobian(&r)
   685  
   686  	for i := 0; i < len(results); i++ {
   687  		got.FromJacobian(&results[i])
   688  		if !expected.Equal(&got) {
   689  			t.Fatalf("cross msm failed with c=%d", cRange[i])
   690  		}
   691  	}
   692  
   693  }
   694  
   695  // _innerMsmG2Reference always do ext jacobian with c == 16
   696  func _innerMsmG2Reference(p *G2Jac, points []G2Affine, scalars []fr.Element, config ecc.MultiExpConfig) *G2Jac {
   697  	// partition the scalars
   698  	digits, _ := partitionScalars(scalars, 16, config.NbTasks)
   699  
   700  	nbChunks := computeNbChunks(16)
   701  
   702  	// for each chunk, spawn one go routine that'll loop through all the scalars in the
   703  	// corresponding bit-window
   704  	// note that buckets is an array allocated on the stack and this is critical for performance
   705  
   706  	// each go routine sends its result in chChunks[i] channel
   707  	chChunks := make([]chan g2JacExtended, nbChunks)
   708  	for i := 0; i < len(chChunks); i++ {
   709  		chChunks[i] = make(chan g2JacExtended, 1)
   710  	}
   711  
   712  	// the last chunk may be processed with a different method than the rest, as it could be smaller.
   713  	n := len(points)
   714  	for j := int(nbChunks - 1); j >= 0; j-- {
   715  		processChunk := processChunkG2Jacobian[bucketg2JacExtendedC16]
   716  		go processChunk(uint64(j), chChunks[j], 16, points, digits[j*n:(j+1)*n], nil)
   717  	}
   718  
   719  	return msmReduceChunkG2Affine(p, int(16), chChunks[:])
   720  }
   721  
   722  func BenchmarkMultiExpG2(b *testing.B) {
   723  
   724  	const (
   725  		pow       = (bits.UintSize / 2) - (bits.UintSize / 8) // 24 on 64 bits arch, 12 on 32 bits
   726  		nbSamples = 1 << pow
   727  	)
   728  
   729  	var (
   730  		samplePoints             [nbSamples]G2Affine
   731  		sampleScalars            [nbSamples]fr.Element
   732  		sampleScalarsSmallValues [nbSamples]fr.Element
   733  		sampleScalarsRedundant   [nbSamples]fr.Element
   734  	)
   735  
   736  	fillBenchScalars(sampleScalars[:])
   737  	copy(sampleScalarsSmallValues[:], sampleScalars[:])
   738  	copy(sampleScalarsRedundant[:], sampleScalars[:])
   739  
   740  	// this means first chunk is going to have more work to do and should be split into several go routines
   741  	for i := 0; i < len(sampleScalarsSmallValues); i++ {
   742  		if i%5 == 0 {
   743  			sampleScalarsSmallValues[i].SetZero()
   744  			sampleScalarsSmallValues[i][0] = 1
   745  		}
   746  	}
   747  
   748  	// bad case for batch affine because scalar distribution might look uniform
   749  	// but over batchSize windows, we may hit a lot of conflicts and force the msm-affine
   750  	// to process small batches of additions to flush its queue of conflicted points.
   751  	for i := 0; i < len(sampleScalarsRedundant); i += 100 {
   752  		for j := i + 1; j < i+100 && j < len(sampleScalarsRedundant); j++ {
   753  			sampleScalarsRedundant[j] = sampleScalarsRedundant[i]
   754  		}
   755  	}
   756  
   757  	fillBenchBasesG2(samplePoints[:])
   758  
   759  	var testPoint G2Affine
   760  
   761  	for i := 5; i <= pow; i++ {
   762  		using := 1 << i
   763  
   764  		b.Run(fmt.Sprintf("%d points", using), func(b *testing.B) {
   765  			b.ResetTimer()
   766  			for j := 0; j < b.N; j++ {
   767  				testPoint.MultiExp(samplePoints[:using], sampleScalars[:using], ecc.MultiExpConfig{})
   768  			}
   769  		})
   770  
   771  		b.Run(fmt.Sprintf("%d points-smallvalues", using), func(b *testing.B) {
   772  			b.ResetTimer()
   773  			for j := 0; j < b.N; j++ {
   774  				testPoint.MultiExp(samplePoints[:using], sampleScalarsSmallValues[:using], ecc.MultiExpConfig{})
   775  			}
   776  		})
   777  
   778  		b.Run(fmt.Sprintf("%d points-redundancy", using), func(b *testing.B) {
   779  			b.ResetTimer()
   780  			for j := 0; j < b.N; j++ {
   781  				testPoint.MultiExp(samplePoints[:using], sampleScalarsRedundant[:using], ecc.MultiExpConfig{})
   782  			}
   783  		})
   784  	}
   785  }
   786  
   787  func BenchmarkMultiExpG2Reference(b *testing.B) {
   788  	const nbSamples = 1 << 20
   789  
   790  	var (
   791  		samplePoints  [nbSamples]G2Affine
   792  		sampleScalars [nbSamples]fr.Element
   793  	)
   794  
   795  	fillBenchScalars(sampleScalars[:])
   796  	fillBenchBasesG2(samplePoints[:])
   797  
   798  	var testPoint G2Affine
   799  
   800  	b.ResetTimer()
   801  	for j := 0; j < b.N; j++ {
   802  		testPoint.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   803  	}
   804  }
   805  
   806  func BenchmarkManyMultiExpG2Reference(b *testing.B) {
   807  	const nbSamples = 1 << 20
   808  
   809  	var (
   810  		samplePoints  [nbSamples]G2Affine
   811  		sampleScalars [nbSamples]fr.Element
   812  	)
   813  
   814  	fillBenchScalars(sampleScalars[:])
   815  	fillBenchBasesG2(samplePoints[:])
   816  
   817  	var t1, t2, t3 G2Affine
   818  	b.ResetTimer()
   819  	for j := 0; j < b.N; j++ {
   820  		var wg sync.WaitGroup
   821  		wg.Add(3)
   822  		go func() {
   823  			t1.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   824  			wg.Done()
   825  		}()
   826  		go func() {
   827  			t2.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   828  			wg.Done()
   829  		}()
   830  		go func() {
   831  			t3.MultiExp(samplePoints[:], sampleScalars[:], ecc.MultiExpConfig{})
   832  			wg.Done()
   833  		}()
   834  		wg.Wait()
   835  	}
   836  }
   837  
   838  // WARNING: this return points that are NOT on the curve and is meant to be use for benchmarking
   839  // purposes only. We don't check that the result is valid but just measure "computational complexity".
   840  //
   841  // Rationale for generating points that are not on the curve is that for large benchmarks, generating
   842  // a vector of different points can take minutes. Using the same point or subset will bias the benchmark result
   843  // since bucket additions in extended jacobian coordinates will hit doubling algorithm instead of add.
   844  func fillBenchBasesG2(samplePoints []G2Affine) {
   845  	var r big.Int
   846  	r.SetString("340444420969191673093399857471996460938405", 10)
   847  	samplePoints[0].ScalarMultiplication(&samplePoints[0], &r)
   848  
   849  	one := samplePoints[0].X
   850  	one.SetOne()
   851  
   852  	for i := 1; i < len(samplePoints); i++ {
   853  		samplePoints[i].X.Add(&samplePoints[i-1].X, &one)
   854  		samplePoints[i].Y.Sub(&samplePoints[i-1].Y, &one)
   855  	}
   856  }
   857  
   858  func fillBenchScalars(sampleScalars []fr.Element) {
   859  	// ensure every words of the scalars are filled
   860  	for i := 0; i < len(sampleScalars); i++ {
   861  		sampleScalars[i].SetRandom()
   862  	}
   863  }