github.com/consensys/gnark-crypto@v0.14.0/field/generator/internal/templates/element/inverse_tests.go (about)

     1  package element
     2  
     3  const InverseTests = `
     4  
     5  {{if $.UsingP20Inverse}}
     6  
     7  func Test{{.ElementName}}InversionApproximation(t *testing.T) {
     8  	var x {{.ElementName}}
     9  	for i := 0; i < 1000; i++ {
    10  		x.SetRandom()
    11  
    12  		// Normally small elements are unlikely. Here we give them a higher chance
    13  		xZeros := mrand.Int() % Limbs //#nosec G404 weak rng is fine here
    14  		for j := 1; j < xZeros; j++ {
    15  			x[Limbs - j] = 0
    16  		}
    17  
    18  		a := approximate(&x, x.BitLen())
    19  		aRef := approximateRef(&x)
    20  
    21  		if a != aRef {
    22  			t.Error("Approximation mismatch")
    23  		}
    24  	}
    25  }
    26  
    27  func Test{{.ElementName}}InversionCorrectionFactorFormula(t *testing.T) {
    28  	const kLimbs = k * Limbs
    29  	const power = kLimbs*6 + invIterationsN*(kLimbs-k+1)
    30  	factorInt := big.NewInt(1)
    31  	factorInt.Lsh(factorInt, power)
    32  	factorInt.Mod(factorInt, Modulus())
    33  
    34  	var refFactorInt big.Int
    35  	inversionCorrectionFactor := {{.ElementName}}{
    36  		{{- range $i := .NbWordsIndexesFull }}
    37  		inversionCorrectionFactorWord{{$i}},
    38  		{{- end}}
    39  	}
    40  	inversionCorrectionFactor.toBigInt(&refFactorInt)
    41  
    42  	if refFactorInt.Cmp(factorInt) != 0 {
    43  		t.Error("mismatch")
    44  	}
    45  }
    46  
    47  func Test{{.ElementName}}LinearComb(t *testing.T) {
    48  	var x {{.ElementName}}
    49  	var y {{.ElementName}}
    50  
    51  	for i := 0; i < 1000; i++ {
    52  		x.SetRandom()
    53  		y.SetRandom()
    54  		testLinearComb(t, &x, mrand.Int63(), &y, mrand.Int63()) //#nosec G404 weak rng is fine here
    55  	}
    56  }
    57  
    58  // Probably unnecessary post-dev. In case the output of inv is wrong, this checks whether it's only off by a constant factor.
    59  func Test{{.ElementName}}InversionCorrectionFactor(t *testing.T) {
    60  
    61  	// (1/x)/inv(x) = (1/1)/inv(1) ⇔ inv(1) = x inv(x)
    62  
    63  	var one {{.ElementName}}
    64  	var oneInv {{.ElementName}}
    65  	one.SetOne()
    66  	oneInv.Inverse(&one)
    67  
    68  	for i := 0; i < 100; i++ {
    69  		var x {{.ElementName}}
    70  		var xInv {{.ElementName}}
    71  		x.SetRandom()
    72  		xInv.Inverse(&x)
    73  
    74  		x.Mul(&x, &xInv)
    75  		if !x.Equal(&oneInv) {
    76  			t.Error("Correction factor is inconsistent")
    77  		}
    78  	}
    79  
    80  	if !oneInv.Equal(&one) {
    81  		var i big.Int
    82  		oneInv.BigInt(&i)	// no montgomery
    83  		i.ModInverse(&i, Modulus())
    84  		var fac {{.ElementName}}
    85  		fac.setBigInt(&i)	// back to montgomery
    86  
    87  		var facTimesFac {{.ElementName}}
    88  		facTimesFac.Mul(&fac, &{{.ElementName}}{
    89  			{{- range $i := .NbWordsIndexesFull }}
    90  			inversionCorrectionFactorWord{{$i}},
    91  			{{- end}}
    92  		})
    93  
    94  		t.Error("Correction factor is consistently off by", fac, "Should be", facTimesFac)
    95  	}
    96  }
    97  
    98  func Test{{.ElementName}}BigNumNeg(t *testing.T) {
    99  	var a {{.ElementName}}
   100  	aHi := negL(&a, 0)
   101  	if !a.IsZero() || aHi != 0 {
   102  		t.Error("-0 != 0")
   103  	}
   104  }
   105  
   106  func Test{{.ElementName}}BigNumWMul(t *testing.T) {
   107  	var x {{.ElementName}}
   108  
   109  	for i := 0; i < 1000; i++ {
   110  		x.SetRandom()
   111  		w := mrand.Int63() //#nosec G404 weak rng is fine here
   112  		testBigNumWMul(t, &x, w)
   113  	}
   114  }
   115  
   116  func Test{{.ElementName}}VeryBigIntConversion(t *testing.T) {
   117  	xHi := mrand.Uint64() //#nosec G404 weak rng is fine here
   118  	var x {{.ElementName}}
   119  	x.SetRandom()
   120  	var xInt big.Int
   121  	x.toVeryBigIntSigned(&xInt, xHi)
   122  	x.assertMatchVeryBigInt(t, xHi, &xInt)
   123  }
   124  
   125  type veryBigInt struct {
   126  	asInt big.Int
   127  	low {{.ElementName}}
   128  	hi uint64
   129  }
   130  
   131  // genVeryBigIntSigned if sign == 0, no sign is forced
   132  func genVeryBigIntSigned(sign int) gopter.Gen {
   133  	return func(genParams *gopter.GenParameters) *gopter.GenResult {
   134  		var g veryBigInt
   135  
   136  		g.low = {{.ElementName}}{
   137  			{{- range $i := .NbWordsIndexesFull}}
   138  			genParams.NextUint64(),
   139  			{{- end}}
   140  		}
   141  
   142  		g.hi = genParams.NextUint64()
   143  
   144  		if sign < 0 {
   145  			g.hi |= signBitSelector
   146  		} else if sign > 0 {
   147  			g.hi &= ^signBitSelector
   148  		}
   149  
   150  		g.low.toVeryBigIntSigned(&g.asInt, g.hi)
   151  
   152  		genResult := gopter.NewGenResult(g, gopter.NoShrinker)
   153  		return genResult
   154  	}
   155  }
   156  
   157  func Test{{.ElementName}}MontReduce(t *testing.T) {
   158  
   159  	parameters := gopter.DefaultTestParameters()
   160  	if testing.Short() {
   161  		parameters.MinSuccessfulTests = nbFuzzShort
   162  	} else {
   163  		parameters.MinSuccessfulTests = nbFuzz
   164  	}
   165  
   166  	properties := gopter.NewProperties(parameters)
   167  
   168  	gen := genVeryBigIntSigned(0)
   169  
   170  	properties.Property("Montgomery reduction is correct", prop.ForAll(
   171  		func(g veryBigInt) bool {
   172  			var res {{.ElementName}}
   173  			var resInt big.Int
   174  
   175  			montReduce(&resInt, &g.asInt)
   176  			res.montReduceSigned(&g.low, g.hi)
   177  
   178  			return res.matchVeryBigInt(0, &resInt) == nil
   179  		},
   180  		gen,
   181  	))
   182  
   183  
   184  
   185  	properties.TestingRun(t, gopter.ConsoleReporter(false))
   186  }
   187  
   188  func Test{{.ElementName}}MontReduceMultipleOfR(t *testing.T) {
   189  
   190  	parameters := gopter.DefaultTestParameters()
   191  	if testing.Short() {
   192  		parameters.MinSuccessfulTests = nbFuzzShort
   193  	} else {
   194  		parameters.MinSuccessfulTests = nbFuzz
   195  	}
   196  
   197  	properties := gopter.NewProperties(parameters)
   198  
   199  	gen := ggen.UInt64()
   200  
   201  	properties.Property("Montgomery reduction is correct", prop.ForAll(
   202  		func(hi uint64) bool {
   203  			var zero, res {{.ElementName}}
   204  			var asInt, resInt big.Int
   205  
   206  			zero.toVeryBigIntSigned(&asInt, hi)
   207  
   208  			montReduce(&resInt, &asInt)
   209  			res.montReduceSigned(&zero, hi)
   210  
   211  			return res.matchVeryBigInt(0, &resInt) == nil
   212  		},
   213  		gen,
   214  	))
   215  
   216  	
   217  
   218  	properties.TestingRun(t, gopter.ConsoleReporter(false))
   219  }
   220  
   221  func Test{{.ElementName}}0Inverse(t *testing.T) {
   222  	var x {{.ElementName}}
   223  	x.Inverse(&x)
   224  	if !x.IsZero() {
   225  		t.Fail()
   226  	}
   227  }
   228  
   229  //TODO: Tests like this (update factor related) are common to all fields. Move them to somewhere non-autogen
   230  func TestUpdateFactorSubtraction(t *testing.T) {
   231  	for i := 0; i < 1000; i++ {
   232  
   233  		f0, g0 := randomizeUpdateFactors()
   234  		f1, g1 := randomizeUpdateFactors()
   235  
   236  		for f0-f1 > 1<<31 || f0-f1 <= -1<<31 {
   237  			f1 /= 2
   238  		}
   239  
   240  		for g0-g1 > 1<<31 || g0-g1 <= -1<<31 {
   241  			g1 /= 2
   242  		}
   243  
   244  		c0 := updateFactorsCompose(f0, g0)
   245  		c1 := updateFactorsCompose(f1, g1)
   246  
   247  		cRes := c0 - c1
   248  		fRes, gRes := updateFactorsDecompose(cRes)
   249  
   250  		if fRes != f0-f1 || gRes != g0-g1 {
   251  			t.Error(i)
   252  		}
   253  	}
   254  }
   255  
   256  func TestUpdateFactorsDouble(t *testing.T) {
   257  	for i := 0; i < 1000; i++ {
   258  		f, g := randomizeUpdateFactors()
   259  
   260  		if f > 1<<30 || f < (-1<<31+1)/2 {
   261  			f /= 2
   262  			if g <= 1<<29 && g >= (-1<<31+1)/4 {
   263  				g *= 2 //g was kept small on f's account. Now that we're halving f, we can double g
   264  			}
   265  		}
   266  
   267  		if g > 1<<30 || g < (-1<<31+1)/2 {
   268  			g /= 2
   269  
   270  			if f <= 1<<29 && f >= (-1<<31+1)/4 {
   271  				f *= 2 //f was kept small on g's account. Now that we're halving g, we can double f
   272  			}
   273  		}
   274  
   275  		c := updateFactorsCompose(f, g)
   276  		cD := c * 2
   277  		fD, gD := updateFactorsDecompose(cD)
   278  
   279  		if fD != 2*f || gD != 2*g {
   280  			t.Error(i)
   281  		}
   282  	}
   283  }
   284  
   285  func TestUpdateFactorsNeg(t *testing.T) {
   286  	var fMistake bool
   287  	for i := 0; i < 1000; i++ {
   288  		f, g := randomizeUpdateFactors()
   289  
   290  		if f == 0x80000000 || g == 0x80000000 {
   291  			// Update factors this large can only have been obtained after 31 iterations and will therefore never be negated
   292  			// We don't have capacity to store -2³¹
   293  			// Repeat this iteration
   294  			i--
   295  			continue
   296  		}
   297  
   298  		c := updateFactorsCompose(f, g)
   299  		nc := -c
   300  		nf, ng := updateFactorsDecompose(nc)
   301  		fMistake = fMistake || nf != -f
   302  		if nf != -f || ng != -g {
   303  			t.Errorf("Mismatch iteration #%d:\n%d, %d ->\n %d -> %d ->\n %d, %d\n Inputs in hex: %X, %X",
   304  				i, f, g, c, nc, nf, ng, f, g)
   305  		}
   306  	}
   307  	if fMistake {
   308  		t.Error("Mistake with f detected")
   309  	} else {
   310  		t.Log("All good with f")
   311  	}
   312  }
   313  
   314  func TestUpdateFactorsNeg0(t *testing.T) {
   315  	c := updateFactorsCompose(0, 0)
   316  	t.Logf("c(0,0) = %X", c)
   317  	cn := -c
   318  
   319  	if c != cn {
   320  		t.Error("Negation of zero update factors should yield the same result.")
   321  	}
   322  }
   323  
   324  func TestUpdateFactorDecomposition(t *testing.T) {
   325  	var negSeen bool
   326  
   327  	for i := 0; i < 1000; i++ {
   328  
   329  		f, g := randomizeUpdateFactors()
   330  
   331  		if f <= -(1<<31) || f > 1<<31 {
   332  			t.Fatal("f out of range")
   333  		}
   334  
   335  		negSeen = negSeen || f < 0
   336  
   337  		c := updateFactorsCompose(f, g)
   338  
   339  		fBack, gBack := updateFactorsDecompose(c)
   340  
   341  		if f != fBack || g != gBack {
   342  			t.Errorf("(%d, %d) -> %d -> (%d, %d)\n", f, g, c, fBack, gBack)
   343  		}
   344  	}
   345  
   346  	if !negSeen {
   347  		t.Fatal("No negative f factors")
   348  	}
   349  }
   350  
   351  func TestUpdateFactorInitialValues(t *testing.T) {
   352  
   353  	f0, g0 := updateFactorsDecompose(updateFactorIdentityMatrixRow0)
   354  	f1, g1 := updateFactorsDecompose(updateFactorIdentityMatrixRow1)
   355  
   356  	if f0 != 1 || g0 != 0 || f1 != 0 || g1 != 1 {
   357  		t.Error("Update factor initial value constants are incorrect")
   358  	}
   359  }
   360  
   361  func TestUpdateFactorsRandomization(t *testing.T) {
   362  	var maxLen int
   363  
   364  	//t.Log("|f| + |g| is not to exceed", 1 << 31)
   365  	for i := 0; i < 1000; i++ {
   366  		f, g := randomizeUpdateFactors()
   367  		lf, lg := abs64T32(f), abs64T32(g)
   368  		absSum := lf + lg
   369  		if absSum >= 1<<31 {
   370  
   371  			if absSum == 1<<31 {
   372  				maxLen++
   373  			} else {
   374  				t.Error(i, "Sum of absolute values too large, f =", f, ",g =", g, ",|f| + |g| =", absSum)
   375  			}
   376  		}
   377  	}
   378  
   379  	if maxLen == 0 {
   380  		t.Error("max len not observed")
   381  	} else {
   382  		t.Log(maxLen, "maxLens observed")
   383  	}
   384  }
   385  
   386  func randomizeUpdateFactor(absLimit uint32) int64 {
   387  	const maxSizeLikelihood = 10
   388  	maxSize := mrand.Intn(maxSizeLikelihood) //#nosec G404 weak rng is fine here
   389  
   390  	absLimit64 := int64(absLimit)
   391  	var f int64
   392  	switch maxSize {
   393  	case 0:
   394  		f = absLimit64
   395  	case 1:
   396  		f = -absLimit64
   397  	default:
   398  		f = int64(mrand.Uint64()%(2*uint64(absLimit64)+1)) - absLimit64 //#nosec G404 weak rng is fine here
   399  	}
   400  
   401  	if f > 1<<31 {
   402  		return 1 << 31
   403  	} else if f < -1<<31+1 {
   404  		return -1<<31 + 1
   405  	}
   406  
   407  	return f
   408  }
   409  
   410  func abs64T32(f int64) uint32 {
   411  	if f >= 1<<32 || f < -1<<32 {
   412  		panic("f out of range")
   413  	}
   414  
   415  	if f < 0 {
   416  		return uint32(-f)
   417  	}
   418  	return uint32(f)
   419  }
   420  
   421  func randomizeUpdateFactors() (int64, int64) {
   422  	var f [2]int64
   423  	b := mrand.Int() % 2 //#nosec G404 weak rng is fine here
   424  
   425  	f[b] = randomizeUpdateFactor(1 << 31)
   426  
   427  	//As per the paper, |f| + |g| \le 2³¹.
   428  	f[1-b] = randomizeUpdateFactor(1<<31 - abs64T32(f[b]))
   429  
   430  	//Patching another edge case
   431  	if f[0]+f[1] == -1<<31 {
   432  		b = mrand.Int() % 2 //#nosec G404 weak rng is fine here
   433  		f[b]++
   434  	}
   435  
   436  	return f[0], f[1]
   437  }
   438  
   439  func testLinearComb(t *testing.T, x *{{.ElementName}}, xC int64, y *{{.ElementName}}, yC int64) {
   440  
   441  	var p1 big.Int
   442  	x.toBigInt(&p1)
   443  	p1.Mul(&p1, big.NewInt(xC))
   444  
   445  	var p2 big.Int
   446  	y.toBigInt(&p2)
   447  	p2.Mul(&p2, big.NewInt(yC))
   448  
   449  	p1.Add(&p1, &p2)
   450  	p1.Mod(&p1, Modulus())
   451  	montReduce(&p1, &p1)
   452  
   453  	var z {{.ElementName}}
   454  	z.linearComb(x, xC, y, yC)
   455  	z.assertMatchVeryBigInt(t, 0, &p1)
   456  }
   457  
   458  func testBigNumWMul(t *testing.T, a *{{.ElementName}}, c int64) {
   459  	var aHi uint64
   460  	var aTimes {{.ElementName}}
   461  	aHi = aTimes.mulWNonModular(a, c)
   462  
   463  	assertMulProduct(t, a, c, &aTimes, aHi)
   464  }
   465  
   466  func updateFactorsCompose(f int64, g int64) int64 {
   467  	return f + g<<32
   468  }
   469  
   470  var rInv big.Int
   471  func montReduce(res *big.Int, x *big.Int) {
   472  	if rInv.BitLen() == 0 {	// initialization
   473  		rInv.SetUint64(1)
   474  		rInv.Lsh(&rInv, Limbs * 64)
   475  		rInv.ModInverse(&rInv, Modulus())
   476  	}
   477  	res.Mul(x, &rInv)
   478  	res.Mod(res, Modulus())
   479  }
   480  
   481  func (z *{{.ElementName}}) toVeryBigIntUnsigned(i *big.Int, xHi uint64) {
   482  	z.toBigInt(i)
   483  	var upperWord big.Int
   484  	upperWord.SetUint64(xHi)
   485  	upperWord.Lsh(&upperWord, Limbs*64)
   486  	i.Add(&upperWord, i)
   487  }
   488  
   489  func (z *{{.ElementName}}) toVeryBigIntSigned(i *big.Int, xHi uint64) {
   490  	z.toVeryBigIntUnsigned(i, xHi)
   491  	if signBitSelector&xHi != 0 {
   492  		twosCompModulus := big.NewInt(1)
   493  		twosCompModulus.Lsh(twosCompModulus, (Limbs+1)*64)
   494  		i.Sub(i, twosCompModulus)
   495  	}
   496  }
   497  
   498  func assertMulProduct(t *testing.T, x *{{.ElementName}}, c int64, result *{{.ElementName}}, resultHi uint64) big.Int {
   499  	var xInt big.Int
   500  	x.toBigInt(&xInt)
   501  
   502  	xInt.Mul(&xInt, big.NewInt(c))
   503  
   504  	result.assertMatchVeryBigInt(t, resultHi, &xInt)
   505  	return xInt
   506  }
   507  
   508  func approximateRef(x *{{.ElementName}}) uint64 {
   509  
   510  	var asInt big.Int
   511  	x.toBigInt(&asInt)
   512  	n := x.BitLen()
   513  
   514  	if n <= 64 {
   515  		return asInt.Uint64()
   516  	}
   517  
   518  	modulus := big.NewInt(1 << 31)
   519  	var lo big.Int
   520  	lo.Mod(&asInt, modulus)
   521  
   522  	modulus.Lsh(modulus, uint(n-64))
   523  	var hi big.Int
   524  	hi.Div(&asInt, modulus)
   525  	hi.Lsh(&hi, 31)
   526  
   527  	hi.Add(&hi, &lo)
   528  	return hi.Uint64()
   529  }
   530  {{- end}}
   531  `