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 `