diff --git a/helpers/static_for.nim b/helpers/static_for.nim index 289e973..0232664 100644 --- a/helpers/static_for.nim +++ b/helpers/static_for.nim @@ -26,3 +26,17 @@ macro staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped ident("unrolledIter_" & $idx & $i), body.replaceNodes(idx, newLit i) ) + +{.experimental: "dynamicBindSym".} + +macro staticFor*(ident: untyped{nkIdent}, choices: typed, body: untyped): untyped = + ## matches + ## staticFor(curve, TestCurves): + ## body + ## and unroll the body for each curve in TestCurves + result = newStmtList() + for choice in choices: + result.add nnkBlockStmt.newTree( + ident($ident & "_" & $choice.intVal), + body.replaceNodes(ident, choice) + ) diff --git a/tests/test_fp12.nim b/tests/test_fp12.nim index e4970b4..1561eb4 100644 --- a/tests/test_fp12.nim +++ b/tests/test_fp12.nim @@ -7,535 +7,27 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - # Standard library - unittest, times, # Internals ../constantine/towers, - ../constantine/config/[common, curves], - ../constantine/arithmetic, + ../constantine/config/curves, # Test utilities - ../helpers/prng_unsafe - -const Iters = 128 - -# Random seed for reproducibility -var rng: RngState -let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 -rng.seed(seed) -echo "test_fp12 xoshiro512** seed: ", seed - -# Import: wrap in field element tests in small procedures -# otherwise they will become globals, -# and will create binary size issues. -# Also due to Nim stack scanning, -# having too many elements on the stack (a couple kB) -# will significantly slow down testing (100x is possible) - -suite "𝔽p12 = 𝔽p6[w] (irreducible polynomial w² - γ)": - test "Comparison sanity checks": - proc test(C: static Curve) = - var z, o {.noInit.}: Fp12[C] - - z.setZero() - o.setOne() - - check: not bool(z == o) - - test(BN254_Snarks) - test(BLS12_381) - - test "Addition, substraction negation are consistent": - proc test(C: static Curve) = - # Try to exercise all code paths for in-place/out-of-place add/sum/sub/diff/double/neg - # (1 - (-a) - b + (-a) - 2a) + (2a + 2b + (-b)) == 1 - var accum {.noInit.}, One {.noInit.}, a{.noInit.}, na{.noInit.}, b{.noInit.}, nb{.noInit.}, a2 {.noInit.}, b2 {.noInit.}: Fp12[C] - - One.setOne() - a = rng.random_unsafe(Fp12[C]) - a2 = a - a2.double() - na.neg(a) - - b = rng.random_unsafe(Fp12[C]) - b2.double(b) - nb.neg(b) - - accum.diff(One, na) - accum -= b - accum += na - accum -= a2 - - var t{.noInit.}: Fp12[C] - t.sum(a2, b2) - t += nb - - accum += t - check: bool accum.isOne() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 1 returns 1": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp12[C] - O.setOne() - O - block: - var r{.noinit.}: Fp12[C] - r.square(One) - check: bool(r == One) - # block: - # var r{.noinit.}: Fp12[C] - # r.prod(One, One) - # check: bool(r == One) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 2 returns 4": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp12[C] - O.setOne() - O - - var Two: Fp12[C] - Two.double(One) - - var Four: Fp12[C] - Four.double(Two) - - block: - var r: Fp12[C] - r.square(Two) - - check: bool(r == Four) - # block: - # var r: Fp12[C] - # r.prod(Two, Two) - - # check: bool(r == Four) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 3 returns 9": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp12[C] - O.setOne() - O - - var Three: Fp12[C] - for _ in 0 ..< 3: - Three += One - - var Nine: Fp12[C] - for _ in 0 ..< 9: - Nine += One - - block: - var u: Fp12[C] - u.square(Three) - - check: bool(u == Nine) - # block: - # var u: Fp12[C] - # u.prod(Three, Three) - - # check: bool(u == Nine) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring -3 returns 9": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp12[C] - O.setOne() - O - - var MinusThree: Fp12[C] - for _ in 0 ..< 3: - MinusThree -= One - - var Nine: Fp12[C] - for _ in 0 ..< 9: - Nine += One - - block: - var u: Fp12[C] - u.square(MinusThree) - - check: bool(u == Nine) - # block: - # var u: Fp12[C] - # u.prod(MinusThree, MinusThree) - - # check: bool(u == Nine) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication by 0 and 1": - template test(C: static Curve, body: untyped) = - block: - proc testInstance() = - let Zero {.inject, used.} = block: - var Z{.noInit.}: Fp12[C] - Z.setZero() - Z - let One {.inject, used.} = block: - var O{.noInit.}: Fp12[C] - O.setOne() - O - - for _ in 0 ..< Iters: - let x {.inject.} = rng.random_unsafe(Fp12[C]) - var r{.noinit, inject.}: Fp12[C] - body - - testInstance() - - # test(BN254_Nogami): - # r.prod(x, Zero) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(Zero, x) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(x, One) - # check: bool(r == x) - # test(BN254_Nogami): - # r.prod(One, x) - # check: bool(r == x) - test(BN254_Snarks): - r.prod(x, Zero) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(Zero, x) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(x, One) - check: bool(r == x) - test(BN254_Snarks): - r.prod(One, x) - check: bool(r == x) - test(BLS12_381): - r.prod(x, Zero) - check: bool(r == Zero) - test(BLS12_381): - r.prod(Zero, x) - check: bool(r == Zero) - test(BLS12_381): - r.prod(x, One) - check: bool(r == x) - test(BLS12_381): - r.prod(One, x) - check: bool(r == x) - # test(BN462): - # r.prod(x, Zero) - # check: bool(r == Zero) - # test(BN462): - # r.prod(Zero, x) - # check: bool(r == Zero) - # test(BN462): - # r.prod(x, One) - # check: bool(r == x) - # test(BN462): - # r.prod(One, x) - # check: bool(r == x) - - test "Multiplication and Squaring are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp12[C]) - var rMul{.noInit.}, rSqr{.noInit.}: Fp12[C] - - rMul.prod(a, a) - rSqr.square(a) - - check: bool(rMul == rSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring the opposite gives the same result": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp12[C]) - var na{.noInit.}: Fp12[C] - na.neg(a) - - var rSqr{.noInit.}, rNegSqr{.noInit.}: Fp12[C] - - rSqr.square(a) - rNegSqr.square(na) - - check: bool(rSqr == rNegSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication and Addition/Substraction are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let factor = rng.random_unsafe(-30..30) - - let a = rng.random_unsafe(Fp12[C]) - - if factor == 0: continue - - var sum{.noInit.}, one{.noInit.}, f{.noInit.}: Fp12[C] - one.setOne() - - if factor < 0: - sum.neg(a) - f.neg(one) - for i in 1 ..< -factor: - sum -= a - f -= one - else: - sum = a - f = one - for i in 1 ..< factor: - sum += a - f += one - - var r{.noInit.}: Fp12[C] - - r.prod(a, f) - - check: bool(r == sum) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Addition is associative and commutative": - proc abelianGroup(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp12[curve]) - let b = rng.random_unsafe(Fp12[curve]) - let c = rng.random_unsafe(Fp12[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp12[curve] - - # r0 = (a + b) + c - tmp1.sum(a, b) - tmp2.sum(tmp1, c) - let r0 = tmp2 - - # r1 = a + (b + c) - tmp1.sum(b, c) - tmp2.sum(a, tmp1) - let r1 = tmp2 - - # r2 = (a + c) + b - tmp1.sum(a, c) - tmp2.sum(tmp1, b) - let r2 = tmp2 - - # r3 = a + (c + b) - tmp1.sum(c, b) - tmp2.sum(a, tmp1) - let r3 = tmp2 - - # r4 = (c + a) + b - tmp1.sum(c, a) - tmp2.sum(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # abelianGroup(BN254_Nogami) - abelianGroup(BN254_Snarks) - abelianGroup(BLS12_377) - abelianGroup(BLS12_381) - # abelianGroup(BN446) - # abelianGroup(FKM12_447) - # abelianGroup(BLS12_461) - # abelianGroup(BN462) - - test "Multiplication is associative and commutative": - proc commutativeRing(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp12[curve]) - let b = rng.random_unsafe(Fp12[curve]) - let c = rng.random_unsafe(Fp12[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp12[curve] - - # r0 = (a * b) * c - tmp1.prod(a, b) - tmp2.prod(tmp1, c) - let r0 = tmp2 - - # r1 = a * (b * c) - tmp1.prod(b, c) - tmp2.prod(a, tmp1) - let r1 = tmp2 - - # r2 = (a * c) * b - tmp1.prod(a, c) - tmp2.prod(tmp1, b) - let r2 = tmp2 - - # r3 = a * (c * b) - tmp1.prod(c, b) - tmp2.prod(a, tmp1) - let r3 = tmp2 - - # r4 = (c * a) * b - tmp1.prod(c, a) - tmp2.prod(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # commutativeRing(BN254_Nogami) - commutativeRing(BN254_Snarks) - commutativeRing(BLS12_377) - commutativeRing(BLS12_381) - # commutativeRing(BN446) - # commutativeRing(FKM12_447) - # commutativeRing(BLS12_461) - # commutativeRing(BN462) - - test "Extension field multiplicative inverse": - proc mulInvOne(curve: static Curve) = - var one: Fp12[curve] - one.setOne() - - block: # Inverse of 1 is 1 - var r {.noInit.}: Fp12[curve] - r.inv(one) - check: bool(r == one) - - var aInv, r{.noInit.}: Fp12[curve] - - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp12[curve]) - - aInv.inv(a) - r.prod(a, aInv) - check: bool(r == one) - - r.prod(aInv, a) - check: bool(r == one) - - # mulInvOne(BN254_Nogami) - mulInvOne(BN254_Snarks) - mulInvOne(BLS12_377) - mulInvOne(BLS12_381) - # mulInvOne(BN446) - # mulInvOne(FKM12_447) - # mulInvOne(BLS12_461) - # mulInvOne(BN462) - - test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion": - proc test(curve: static Curve) = - var z: Fp12[curve] - z.setZero() - - var zInv{.noInit.}: Fp12[curve] - - zInv.inv(z) - check: bool zInv.isZero() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) + ./test_fp_tower_template + +const TestCurves = [ + # BN254_Nogami + BN254_Snarks, + BLS12_377, + BLS12_381, + # BN446 + # FKM12_447 + # BLS12_461 + # BN462 + ] + +runTowerTests( + ExtDegree = 12, + Iters = 128, + TestCurves = TestCurves, + moduleName = "test_fp12", + testSuiteDesc = "𝔽p12 = 𝔽p6[w] (irreducible polynomial w²-γ = 0) -> 𝔽p12 point (a, b) with coordinate a + bw and γ quadratic non-residue in 𝔽p6" +) diff --git a/tests/test_fp2.nim b/tests/test_fp2.nim index c3b16be..d83c5c6 100644 --- a/tests/test_fp2.nim +++ b/tests/test_fp2.nim @@ -7,425 +7,27 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - # Standard library - unittest, times, # Internals ../constantine/towers, - ../constantine/config/[common, curves], - ../constantine/arithmetic, + ../constantine/config/curves, # Test utilities - ../helpers/prng_unsafe - -const Iters = 128 - -# Random seed for reproducibility -var rng: RngState -let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 -rng.seed(seed) -echo "test_fp2 xoshiro512** seed: ", seed - -# Import: wrap in field element tests in small procedures -# otherwise they will become globals, -# and will create binary size issues. -# Also due to Nim stack scanning, -# having too many elements on the stack (a couple kB) -# will significantly slow down testing (100x is possible) - -suite "𝔽p2 = 𝔽p[µ] (irreducible polynomial x²+µ)": - test "Comparison sanity checks": - proc test(C: static Curve) = - var z, o {.noInit.}: Fp2[C] - - z.setZero() - o.setOne() - - check: not bool(z == o) - - test(BN254_Snarks) - test(BLS12_381) - - test "Fp2 '1' coordinates in canonical domain": - template test(C: static Curve) = - block: - proc testInstance() = - let oneFp2 = block: - var O{.noInit.}: Fp2[C] - O.setOne() - O - let oneBig = block: - var O{.noInit.}: typeof(C.Mod) - O.setOne() - O - - var r: typeof(C.Mod) - r.redc(oneFp2.c0.mres, C.Mod, C.getNegInvModWord(), canUseNoCarryMontyMul = false) - - check: - bool(r == oneBig) - bool(oneFp2.c1.mres.isZero()) - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_381) - - test "Addition, substraction negation are consistent": - proc test(C: static Curve) = - # Try to exercise all code paths for in-place/out-of-place add/sum/sub/diff/double/neg - # (1 - (-a) - b + (-a) - 2a) + (2a + 2b + (-b)) == 1 - var accum {.noInit.}, One {.noInit.}, a{.noInit.}, na{.noInit.}, b{.noInit.}, nb{.noInit.}, a2 {.noInit.}, b2 {.noInit.}: Fp2[C] - - One.setOne() - a = rng.random_unsafe(Fp2[C]) - a2 = a - a2.double() - na.neg(a) - - b = rng.random_unsafe(Fp2[C]) - b2.double(b) - nb.neg(b) - - accum.diff(One, na) - accum -= b - accum += na - accum -= a2 - - var t{.noInit.}: Fp2[C] - t.sum(a2, b2) - t += nb - - accum += t - check: bool accum.isOne() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 1 returns 1": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp2[C] - O.setOne() - O - block: - var r{.noinit.}: Fp2[C] - r.square(One) - check: bool(r == One) - block: - var r{.noinit.}: Fp2[C] - r.prod(One, One) - check: bool(r == One) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication by 0 and 1": - template test(C: static Curve, body: untyped) = - block: - proc testInstance() = - let Zero {.inject.} = block: - var Z{.noInit.}: Fp2[C] - Z.setZero() - Z - let One {.inject.} = block: - var O{.noInit.}: Fp2[C] - O.setOne() - O - - for _ in 0 ..< Iters: - let x {.inject.} = rng.random_unsafe(Fp2[C]) - var r{.noinit, inject.}: Fp2[C] - body - - testInstance() - - # test(BN254_Nogami): - # r.prod(x, Zero) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(Zero, x) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(x, One) - # check: bool(r == x) - # test(BN254_Nogami): - # r.prod(One, x) - # check: bool(r == x) - test(BN254_Snarks): - r.prod(x, Zero) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(Zero, x) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(x, One) - check: bool(r == x) - test(BN254_Snarks): - r.prod(One, x) - check: bool(r == x) - test(BLS12_381): - r.prod(x, Zero) - check: bool(r == Zero) - test(BLS12_381): - r.prod(Zero, x) - check: bool(r == Zero) - test(BLS12_381): - r.prod(x, One) - check: bool(r == x) - test(BLS12_381): - r.prod(One, x) - check: bool(r == x) - - test "Multiplication and Squaring are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp2[C]) - var rMul{.noInit.}, rSqr{.noInit.}: Fp2[C] - - rMul.prod(a, a) - rSqr.square(a) - - check: bool(rMul == rSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring the opposite gives the same result": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp2[C]) - var na{.noInit.}: Fp2[C] - na.neg(a) - - var rSqr{.noInit.}, rNegSqr{.noInit.}: Fp2[C] - - rSqr.square(a) - rNegSqr.square(na) - - check: bool(rSqr == rNegSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication and Addition/Substraction are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let factor = rng.random_unsafe(-30..30) - - let a = rng.random_unsafe(Fp2[C]) - - if factor == 0: continue - - var sum{.noInit.}, one{.noInit.}, f{.noInit.}: Fp2[C] - one.setOne() - - if factor < 0: - sum.neg(a) - f.neg(one) - for i in 1 ..< -factor: - sum -= a - f -= one - else: - sum = a - f = one - for i in 1 ..< factor: - sum += a - f += one - - var r{.noInit.}: Fp2[C] - - r.prod(a, f) - - check: bool(r == sum) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Addition is associative and commutative": - proc abelianGroup(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp2[curve]) - let b = rng.random_unsafe(Fp2[curve]) - let c = rng.random_unsafe(Fp2[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp2[curve] - - # r0 = (a + b) + c - tmp1.sum(a, b) - tmp2.sum(tmp1, c) - let r0 = tmp2 - - # r1 = a + (b + c) - tmp1.sum(b, c) - tmp2.sum(a, tmp1) - let r1 = tmp2 - - # r2 = (a + c) + b - tmp1.sum(a, c) - tmp2.sum(tmp1, b) - let r2 = tmp2 - - # r3 = a + (c + b) - tmp1.sum(c, b) - tmp2.sum(a, tmp1) - let r3 = tmp2 - - # r4 = (c + a) + b - tmp1.sum(c, a) - tmp2.sum(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # abelianGroup(BN254_Nogami) - abelianGroup(BN254_Snarks) - abelianGroup(BLS12_377) - abelianGroup(BLS12_381) - # abelianGroup(BN446) - # abelianGroup(FKM12_447) - # abelianGroup(BLS12_461) - # abelianGroup(BN462) - - test "Multiplication is associative and commutative": - proc commutativeRing(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp2[curve]) - let b = rng.random_unsafe(Fp2[curve]) - let c = rng.random_unsafe(Fp2[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp2[curve] - - # r0 = (a * b) * c - tmp1.prod(a, b) - tmp2.prod(tmp1, c) - let r0 = tmp2 - - # r1 = a * (b * c) - tmp1.prod(b, c) - tmp2.prod(a, tmp1) - let r1 = tmp2 - - # r2 = (a * c) * b - tmp1.prod(a, c) - tmp2.prod(tmp1, b) - let r2 = tmp2 - - # r3 = a * (c * b) - tmp1.prod(c, b) - tmp2.prod(a, tmp1) - let r3 = tmp2 - - # r4 = (c * a) * b - tmp1.prod(c, a) - tmp2.prod(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # commutativeRing(BN254_Nogami) - commutativeRing(BN254_Snarks) - commutativeRing(BLS12_377) - commutativeRing(BLS12_381) - # commutativeRing(BN446) - # commutativeRing(FKM12_447) - # commutativeRing(BLS12_461) - # commutativeRing(BN462) - - test "Extension field multiplicative inverse": - proc mulInvOne(curve: static Curve) = - var one: Fp2[curve] - one.setOne() - - var aInv, r{.noInit.}: Fp2[curve] - - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp2[curve]) - aInv.inv(a) - r.prod(a, aInv) - check: bool(r == one) - r.prod(aInv, a) - check: bool(r == one) - - # mulInvOne(BN254_Nogami) - mulInvOne(BN254_Snarks) - mulInvOne(BLS12_377) - mulInvOne(BLS12_381) - # mulInvOne(BN446) - # mulInvOne(FKM12_447) - # mulInvOne(BLS12_461) - # mulInvOne(BN462) - - test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion": - proc test(curve: static Curve) = - var z: Fp2[curve] - z.setZero() - - var zInv{.noInit.}: Fp2[curve] - - zInv.inv(z) - check: bool zInv.isZero() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) + ./test_fp_tower_template + +const TestCurves = [ + # BN254_Nogami + BN254_Snarks, + BLS12_377, + BLS12_381, + # BN446 + # FKM12_447 + # BLS12_461 + # BN462 + ] + +runTowerTests( + ExtDegree = 2, + Iters = 128, + TestCurves = TestCurves, + moduleName = "test_fp2", + testSuiteDesc = "𝔽p2 = 𝔽p[u] (irreducible polynomial u²-β = 0) -> 𝔽p2 point (a, b) with coordinate a + bu and β quadratic non-residue in 𝔽p" +) diff --git a/tests/test_fp6.nim b/tests/test_fp6.nim index 7185db0..bc535e0 100644 --- a/tests/test_fp6.nim +++ b/tests/test_fp6.nim @@ -7,535 +7,27 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - # Standard library - unittest, times, # Internals ../constantine/towers, - ../constantine/config/[common, curves], - ../constantine/arithmetic, + ../constantine/config/curves, # Test utilities - ../helpers/prng_unsafe - -const Iters = 128 - -# Random seed for reproducibility -var rng: RngState -let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 -rng.seed(seed) -echo "test_fp6 xoshiro512** seed: ", seed - -# Import: wrap in field element tests in small procedures -# otherwise they will become globals, -# and will create binary size issues. -# Also due to Nim stack scanning, -# having too many elements on the stack (a couple kB) -# will significantly slow down testing (100x is possible) - -suite "𝔽p6 = 𝔽p2[v] (irreducible polynomial v³ - ξ)": - test "Comparison sanity checks": - proc test(C: static Curve) = - var z, o {.noInit.}: Fp6[C] - - z.setZero() - o.setOne() - - check: not bool(z == o) - - test(BN254_Snarks) - test(BLS12_381) - - test "Addition, substraction negation are consistent": - proc test(C: static Curve) = - # Try to exercise all code paths for in-place/out-of-place add/sum/sub/diff/double/neg - # (1 - (-a) - b + (-a) - 2a) + (2a + 2b + (-b)) == 1 - var accum {.noInit.}, One {.noInit.}, a{.noInit.}, na{.noInit.}, b{.noInit.}, nb{.noInit.}, a2 {.noInit.}, b2 {.noInit.}: Fp6[C] - - One.setOne() - a = rng.random_unsafe(Fp6[C]) - a2 = a - a2.double() - na.neg(a) - - b = rng.random_unsafe(Fp6[C]) - b2.double(b) - nb.neg(b) - - accum.diff(One, na) - accum -= b - accum += na - accum -= a2 - - var t{.noInit.}: Fp6[C] - t.sum(a2, b2) - t += nb - - accum += t - check: bool accum.isOne() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 1 returns 1": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp6[C] - O.setOne() - O - block: - var r{.noinit.}: Fp6[C] - r.square(One) - check: bool(r == One) - block: - var r{.noinit.}: Fp6[C] - r.prod(One, One) - check: bool(r == One) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 2 returns 4": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp6[C] - O.setOne() - O - - var Two: Fp6[C] - Two.double(One) - - var Four: Fp6[C] - Four.double(Two) - - block: - var r: Fp6[C] - r.square(Two) - - check: bool(r == Four) - block: - var r: Fp6[C] - r.prod(Two, Two) - - check: bool(r == Four) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring 3 returns 9": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp6[C] - O.setOne() - O - - var Three: Fp6[C] - for _ in 0 ..< 3: - Three += One - - var Nine: Fp6[C] - for _ in 0 ..< 9: - Nine += One - - block: - var u: Fp6[C] - u.square(Three) - - check: bool(u == Nine) - block: - var u: Fp6[C] - u.prod(Three, Three) - - check: bool(u == Nine) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring -3 returns 9": - template test(C: static Curve) = - block: - proc testInstance() = - let One = block: - var O{.noInit.}: Fp6[C] - O.setOne() - O - - var MinusThree: Fp6[C] - for _ in 0 ..< 3: - MinusThree -= One - - var Nine: Fp6[C] - for _ in 0 ..< 9: - Nine += One - - block: - var u: Fp6[C] - u.square(MinusThree) - - check: bool(u == Nine) - block: - var u: Fp6[C] - u.prod(MinusThree, MinusThree) - - check: bool(u == Nine) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication by 0 and 1": - template test(C: static Curve, body: untyped) = - block: - proc testInstance() = - let Zero {.inject.} = block: - var Z{.noInit.}: Fp6[C] - Z.setZero() - Z - let One {.inject.} = block: - var O{.noInit.}: Fp6[C] - O.setOne() - O - - for _ in 0 ..< Iters: - let x {.inject.} = rng.random_unsafe(Fp6[C]) - var r{.noinit, inject.}: Fp6[C] - body - - testInstance() - - # test(BN254_Nogami): - # r.prod(x, Zero) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(Zero, x) - # check: bool(r == Zero) - # test(BN254_Nogami): - # r.prod(x, One) - # check: bool(r == x) - # test(BN254_Nogami): - # r.prod(One, x) - # check: bool(r == x) - test(BN254_Snarks): - r.prod(x, Zero) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(Zero, x) - check: bool(r == Zero) - test(BN254_Snarks): - r.prod(x, One) - check: bool(r == x) - test(BN254_Snarks): - r.prod(One, x) - check: bool(r == x) - test(BLS12_381): - r.prod(x, Zero) - check: bool(r == Zero) - test(BLS12_381): - r.prod(Zero, x) - check: bool(r == Zero) - test(BLS12_381): - r.prod(x, One) - check: bool(r == x) - test(BLS12_381): - r.prod(One, x) - check: bool(r == x) - # test(BN462): - # r.prod(x, Zero) - # check: bool(r == Zero) - # test(BN462): - # r.prod(Zero, x) - # check: bool(r == Zero) - # test(BN462): - # r.prod(x, One) - # check: bool(r == x) - # test(BN462): - # r.prod(One, x) - # check: bool(r == x) - - test "Multiplication and Squaring are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp6[C]) - var rMul{.noInit.}, rSqr{.noInit.}: Fp6[C] - - rMul.prod(a, a) - rSqr.square(a) - - check: bool(rMul == rSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Squaring the opposite gives the same result": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp6[C]) - var na{.noInit.}: Fp6[C] - na.neg(a) - - var rSqr{.noInit.}, rNegSqr{.noInit.}: Fp6[C] - - rSqr.square(a) - rNegSqr.square(na) - - check: bool(rSqr == rNegSqr) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Multiplication and Addition/Substraction are consistent": - template test(C: static Curve) = - block: - proc testInstance() = - for _ in 0 ..< Iters: - let factor = rng.random_unsafe(-30..30) - - let a = rng.random_unsafe(Fp6[C]) - - if factor == 0: continue - - var sum{.noInit.}, one{.noInit.}, f{.noInit.}: Fp6[C] - one.setOne() - - if factor < 0: - sum.neg(a) - f.neg(one) - for i in 1 ..< -factor: - sum -= a - f -= one - else: - sum = a - f = one - for i in 1 ..< factor: - sum += a - f += one - - var r{.noInit.}: Fp6[C] - - r.prod(a, f) - - check: bool(r == sum) - - testInstance() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) - - test "Addition is associative and commutative": - proc abelianGroup(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp6[curve]) - let b = rng.random_unsafe(Fp6[curve]) - let c = rng.random_unsafe(Fp6[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp6[curve] - - # r0 = (a + b) + c - tmp1.sum(a, b) - tmp2.sum(tmp1, c) - let r0 = tmp2 - - # r1 = a + (b + c) - tmp1.sum(b, c) - tmp2.sum(a, tmp1) - let r1 = tmp2 - - # r2 = (a + c) + b - tmp1.sum(a, c) - tmp2.sum(tmp1, b) - let r2 = tmp2 - - # r3 = a + (c + b) - tmp1.sum(c, b) - tmp2.sum(a, tmp1) - let r3 = tmp2 - - # r4 = (c + a) + b - tmp1.sum(c, a) - tmp2.sum(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # abelianGroup(BN254_Nogami) - abelianGroup(BN254_Snarks) - abelianGroup(BLS12_377) - abelianGroup(BLS12_381) - # abelianGroup(BN446) - # abelianGroup(FKM12_447) - # abelianGroup(BLS12_461) - # abelianGroup(BN462) - - test "Multiplication is associative and commutative": - proc commutativeRing(curve: static Curve) = - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp6[curve]) - let b = rng.random_unsafe(Fp6[curve]) - let c = rng.random_unsafe(Fp6[curve]) - - var tmp1{.noInit.}, tmp2{.noInit.}: Fp6[curve] - - # r0 = (a * b) * c - tmp1.prod(a, b) - tmp2.prod(tmp1, c) - let r0 = tmp2 - - # r1 = a * (b * c) - tmp1.prod(b, c) - tmp2.prod(a, tmp1) - let r1 = tmp2 - - # r2 = (a * c) * b - tmp1.prod(a, c) - tmp2.prod(tmp1, b) - let r2 = tmp2 - - # r3 = a * (c * b) - tmp1.prod(c, b) - tmp2.prod(a, tmp1) - let r3 = tmp2 - - # r4 = (c * a) * b - tmp1.prod(c, a) - tmp2.prod(tmp1, b) - let r4 = tmp2 - - # ... - - check: - bool(r0 == r1) - bool(r0 == r2) - bool(r0 == r3) - bool(r0 == r4) - - # commutativeRing(BN254_Nogami) - commutativeRing(BN254_Snarks) - commutativeRing(BLS12_377) - commutativeRing(BLS12_381) - # commutativeRing(BN446) - # commutativeRing(FKM12_447) - # commutativeRing(BLS12_461) - # commutativeRing(BN462) - - test "Extension field multiplicative inverse": - proc mulInvOne(curve: static Curve) = - var one: Fp6[curve] - one.setOne() - - block: # Inverse of 1 is 1 - var r {.noInit.}: Fp6[curve] - r.inv(one) - check: bool(r == one) - - var aInv, r{.noInit.}: Fp6[curve] - - for _ in 0 ..< Iters: - let a = rng.random_unsafe(Fp6[curve]) - - aInv.inv(a) - r.prod(a, aInv) - check: bool(r == one) - - r.prod(aInv, a) - check: bool(r == one) - - # mulInvOne(BN254_Nogami) - mulInvOne(BN254_Snarks) - mulInvOne(BLS12_377) - mulInvOne(BLS12_381) - # mulInvOne(BN446) - # mulInvOne(FKM12_447) - # mulInvOne(BLS12_461) - # mulInvOne(BN462) - - test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion": - proc test(curve: static Curve) = - var z: Fp6[curve] - z.setZero() - - var zInv{.noInit.}: Fp6[curve] - - zInv.inv(z) - check: bool zInv.isZero() - - # test(BN254_Nogami) - test(BN254_Snarks) - test(BLS12_377) - test(BLS12_381) - # test(BN446) - # test(FKM12_447) - # test(BLS12_461) - # test(BN462) + ./test_fp_tower_template + +const TestCurves = [ + # BN254_Nogami + BN254_Snarks, + BLS12_377, + BLS12_381, + # BN446 + # FKM12_447 + # BLS12_461 + # BN462 + ] + +runTowerTests( + ExtDegree = 6, + Iters = 128, + TestCurves = TestCurves, + moduleName = "test_fp6", + testSuiteDesc = "𝔽p6 = 𝔽p2[v] (irreducible polynomial v³-ξ = 0) -> 𝔽p6 point (a, b, c) with coordinate a + bv + cv² and ξ cubic non-residue in 𝔽p2" +) diff --git a/tests/test_fp_tower_template.nim b/tests/test_fp_tower_template.nim new file mode 100644 index 0000000..aa39e3f --- /dev/null +++ b/tests/test_fp_tower_template.nim @@ -0,0 +1,412 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +# ############################################################ +# +# Template tests for towered extension fields +# +# ############################################################ + + +import + # Standard library + std/[unittest, times], + # Internals + ../constantine/towers, + ../constantine/config/[common, curves], + ../constantine/arithmetic, + # Test utilities + ../helpers/[prng_unsafe, static_for] + +template ExtField(degree: static int, curve: static Curve): untyped = + when degree == 2: + Fp2[curve] + elif degree == 6: + Fp6[curve] + elif degree == 12: + Fp12[curve] + else: + {.error: "Unconfigured extension degree".} + +proc runTowerTests*[N]( + ExtDegree: static int, + Iters: static int, + TestCurves: static array[N, Curve], + moduleName: string, + testSuiteDesc: string + ) = + + # Random seed for reproducibility + var rng: RngState + let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 + rng.seed(seed) + echo moduleName, " xoshiro512** seed: ", seed + + suite testSuiteDesc: + test "Comparison sanity checks": + proc test(Field: typedesc) = + var z, o {.noInit.}: Field + + z.setZero() + o.setOne() + + check: not bool(z == o) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Addition, substraction negation are consistent": + proc test(Field: typedesc) = + # Try to exercise all code paths for in-place/out-of-place add/sum/sub/diff/double/neg + # (1 - (-a) - b + (-a) - 2a) + (2a + 2b + (-b)) == 1 + var accum {.noInit.}, One {.noInit.}, a{.noInit.}, na{.noInit.}, b{.noInit.}, nb{.noInit.}, a2 {.noInit.}, b2 {.noInit.}: Field + + One.setOne() + a = rng.random_unsafe(Field) + a2 = a + a2.double() + na.neg(a) + + b = rng.random_unsafe(Field) + b2.double(b) + nb.neg(b) + + accum.diff(One, na) + accum -= b + accum += na + accum -= a2 + + var t{.noInit.}: Field + t.sum(a2, b2) + t += nb + + accum += t + check: bool accum.isOne() + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Squaring 1 returns 1": + proc test(Field: typedesc) = + let One = block: + var O{.noInit.}: Field + O.setOne() + O + block: + var r{.noinit.}: Field + r.square(One) + check: bool(r == One) + block: + var r{.noinit.}: Field + r.prod(One, One) + check: bool(r == One) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Squaring 2 returns 4": + proc test(Field: typedesc) = + let One = block: + var O{.noInit.}: Field + O.setOne() + O + + var Two: Field + Two.double(One) + + var Four: Field + Four.double(Two) + + block: + var r: Field + r.square(Two) + + check: bool(r == Four) + block: + var r: Field + r.prod(Two, Two) + + check: bool(r == Four) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Squaring 3 returns 9": + proc test(Field: typedesc) = + let One = block: + var O{.noInit.}: Field + O.setOne() + O + + var Three: Field + for _ in 0 ..< 3: + Three += One + + var Nine: Field + for _ in 0 ..< 9: + Nine += One + + block: + var u: Field + u.square(Three) + + check: bool(u == Nine) + block: + var u: Field + u.prod(Three, Three) + + check: bool(u == Nine) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Squaring -3 returns 9": + proc test(Field: typedesc) = + let One = block: + var O{.noInit.}: Field + O.setOne() + O + + var MinusThree: Field + for _ in 0 ..< 3: + MinusThree -= One + + var Nine: Field + for _ in 0 ..< 9: + Nine += One + + block: + var u: Field + u.square(MinusThree) + + check: bool(u == Nine) + block: + var u: Field + u.prod(MinusThree, MinusThree) + + check: bool(u == Nine) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)) + + test "Multiplication by 0 and 1": + template test(Field: typedesc, body: untyped) = + block: + proc testInstance() = + let Z {.inject.} = block: + var Z{.noInit.}: Field + Z.setZero() + Z + let O {.inject.} = block: + var O{.noInit.}: Field + O.setOne() + O + + for _ in 0 ..< Iters: + let x {.inject.} = rng.random_unsafe(Field) + var r{.noinit, inject.}: Field + body + + testInstance() + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve)): + r.prod(x, Z) + check: bool(r == Z) + test(ExtField(ExtDegree, curve)): + r.prod(Z, x) + check: bool(r == Z) + test(ExtField(ExtDegree, curve)): + r.prod(x, O) + check: bool(r == x) + test(ExtField(ExtDegree, curve)): + r.prod(O, x) + check: bool(r == x) + + test "Multiplication and Squaring are consistent": + proc test(Field: typedesc, Iters: static int) = + for _ in 0 ..< Iters: + let a = rng.random_unsafe(Field) + var rMul{.noInit.}, rSqr{.noInit.}: Field + + rMul.prod(a, a) + rSqr.square(a) + + check: bool(rMul == rSqr) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "Squaring the opposite gives the same result": + proc test(Field: typedesc, Iters: static int) = + for _ in 0 ..< Iters: + let a = rng.random_unsafe(Field) + var na{.noInit.}: Field + na.neg(a) + + var rSqr{.noInit.}, rNegSqr{.noInit.}: Field + + rSqr.square(a) + rNegSqr.square(na) + + check: bool(rSqr == rNegSqr) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "Multiplication and Addition/Substraction are consistent": + proc test(Field: typedesc, Iters: static int) = + for _ in 0 ..< Iters: + let factor = rng.random_unsafe(-30..30) + + let a = rng.random_unsafe(Field) + + if factor == 0: continue + + var sum{.noInit.}, one{.noInit.}, f{.noInit.}: Field + one.setOne() + + if factor < 0: + sum.neg(a) + f.neg(one) + for i in 1 ..< -factor: + sum -= a + f -= one + else: + sum = a + f = one + for i in 1 ..< factor: + sum += a + f += one + + var r{.noInit.}: Field + + r.prod(a, f) + + check: bool(r == sum) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "Addition is associative and commutative": + proc test(Field: typedesc, Iters: static int) = + for _ in 0 ..< Iters: + let a = rng.random_unsafe(Field) + let b = rng.random_unsafe(Field) + let c = rng.random_unsafe(Field) + + var tmp1{.noInit.}, tmp2{.noInit.}: Field + + # r0 = (a + b) + c + tmp1.sum(a, b) + tmp2.sum(tmp1, c) + let r0 = tmp2 + + # r1 = a + (b + c) + tmp1.sum(b, c) + tmp2.sum(a, tmp1) + let r1 = tmp2 + + # r2 = (a + c) + b + tmp1.sum(a, c) + tmp2.sum(tmp1, b) + let r2 = tmp2 + + # r3 = a + (c + b) + tmp1.sum(c, b) + tmp2.sum(a, tmp1) + let r3 = tmp2 + + # r4 = (c + a) + b + tmp1.sum(c, a) + tmp2.sum(tmp1, b) + let r4 = tmp2 + + # ... + + check: + bool(r0 == r1) + bool(r0 == r2) + bool(r0 == r3) + bool(r0 == r4) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "Multiplication is associative and commutative": + proc test(Field: typedesc, Iters: static int) = + for _ in 0 ..< Iters: + let a = rng.random_unsafe(Field) + let b = rng.random_unsafe(Field) + let c = rng.random_unsafe(Field) + + var tmp1{.noInit.}, tmp2{.noInit.}: Field + + # r0 = (a * b) * c + tmp1.prod(a, b) + tmp2.prod(tmp1, c) + let r0 = tmp2 + + # r1 = a * (b * c) + tmp1.prod(b, c) + tmp2.prod(a, tmp1) + let r1 = tmp2 + + # r2 = (a * c) * b + tmp1.prod(a, c) + tmp2.prod(tmp1, b) + let r2 = tmp2 + + # r3 = a * (c * b) + tmp1.prod(c, b) + tmp2.prod(a, tmp1) + let r3 = tmp2 + + # r4 = (c * a) * b + tmp1.prod(c, a) + tmp2.prod(tmp1, b) + let r4 = tmp2 + + # ... + + check: + bool(r0 == r1) + bool(r0 == r2) + bool(r0 == r3) + bool(r0 == r4) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "Extension field multiplicative inverse": + proc test(Field: typedesc, Iters: static int) = + var aInv, r{.noInit.}: Field + + for _ in 0 ..< Iters: + let a = rng.random_unsafe(Field) + aInv.inv(a) + r.prod(a, aInv) + check: bool(r.isOne()) + r.prod(aInv, a) + check: bool(r.isOne()) + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve), Iters) + + test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion": + proc test(Field: typedesc) = + var z: Field + z.setZero() + + var zInv{.noInit.}: Field + + zInv.inv(z) + check: bool zInv.isZero() + + staticFor(curve, TestCurves): + test(ExtField(ExtDegree, curve))