From e491f3b91dea95273c09052bf4f38a9773fe4bd5 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Sat, 20 Jun 2020 18:55:27 +0200 Subject: [PATCH] [WIP] Skewed RNGs that trigger corner cases (#59) * Add a RNG skewed to high hamming weights * Add libsecp256k1 skewed RNG that found a CVE in OpenSSL * Add initial skewed RNGs tests to finite fields * Add Fp towers skewed tests * Add ellptic curve skewed tests --- helpers/prng_unsafe.nim | 279 +++++++++++++++++++++++----- tests/t_ec_template.nim | 184 ++++++++++-------- tests/t_finite_fields_mulsquare.nim | 32 ++++ tests/t_finite_fields_powinv.nim | 36 ++++ tests/t_finite_fields_sqrt.nim | 43 +++-- tests/t_fp_tower_template.nim | 88 ++++++--- 6 files changed, 496 insertions(+), 166 deletions(-) diff --git a/helpers/prng_unsafe.nim b/helpers/prng_unsafe.nim index ad745d8..6d98a19 100644 --- a/helpers/prng_unsafe.nim +++ b/helpers/prng_unsafe.nim @@ -8,8 +8,10 @@ import ../constantine/arithmetic/bigints, + ../constantine/primitives, ../constantine/config/[common, curves], - ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective] + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective], + ../constantine/io/io_bigints # ############################################################ # @@ -79,17 +81,65 @@ func next(rng: var RngState): uint64 = rng.s[7] = rotl(rng.s[7], 21); +# Integer ranges +# ------------------------------------------------------------ + +func random_unsafe*(rng: var RngState, maxExclusive: uint32): uint32 = + ## Generate a random integer in 0 ..< maxExclusive + ## Uses an unbiaised generation method + ## See Lemire's algorithm modified by Melissa O'Neill + ## https://www.pcg-random.org/posts/bounded-rands.html + let max = maxExclusive + var x = uint32 rng.next() + var m = x.uint64 * max.uint64 + var l = uint32 m + if l < max: + var t = not(max) + 1 # -max + if t >= max: + t -= max + if t >= max: + t = t mod max + while l < t: + x = uint32 rng.next() + m = x.uint64 * max.uint64 + l = uint32 m + return uint32(m shr 32) + +func random_unsafe*[T: SomeInteger](rng: var RngState, inclRange: Slice[T]): T = + ## Return a random integer in the given range. + ## The range bounds must fit in an int32. + let maxExclusive = inclRange.b + 1 - inclRange.a + result = T(rng.random_unsafe(uint32 maxExclusive)) + result += inclRange.a + +# Containers +# ------------------------------------------------------------ + +func sample_unsafe*[T](rng: var RngState, src: openarray[T]): T = + ## Return a random sample from an array + result = src[rng.random_unsafe(uint32 src.len)] + # BigInts and Fields # ------------------------------------------------------------ +# +# Statistics note: +# - A skewed distribution is not symmetric, it has a longer tail in one direction. +# for example a RNG that is not centered over 0.5 distribution of 0 and 1 but +# might produces more 1 than 0 or vice-versa. +# - A bias is a result that is consistently off from the true value i.e. +# a deviation of an estimate from the quantity under observation + +func random_unsafe(rng: var RngState, a: var BigInt) = + ## Initialize a standalone BigInt + for i in 0 ..< a.limbs.len: + a.limbs[i] = SecretWord(rng.next()) func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) = ## Recursively initialize a BigInt (part of a field) or Field element ## Unsafe: for testing and benchmarking purposes only when T is BigInt: var reduced, unreduced{.noInit.}: T - - for i in 0 ..< unreduced.limbs.len: - unreduced.limbs[i] = SecretWord(rng.next()) + rng.random_unsafe(unreduced) # Note: a simple modulo will be biaised but it's simple and "fast" reduced.reduce(unreduced, C.Mod) @@ -99,10 +149,79 @@ func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) = for field in fields(a): rng.random_unsafe(field, C) -func random_unsafe(rng: var RngState, a: var BigInt) = +func random_word_highHammingWeight(rng: var RngState): BaseType = + let numZeros = rng.random_unsafe(WordBitWidth div 3) # Average Hamming Weight is 1-0.33/2 = 0.83 + result = high(BaseType) + for _ in 0 ..< numZeros: + result = result.clearBit rng.random_unsafe(WordBitWidth) + +func random_highHammingWeight(rng: var RngState, a: var BigInt) = ## Initialize a standalone BigInt + ## with high Hamming weight + ## to have a higher probability of triggering carries for i in 0 ..< a.limbs.len: - a.limbs[i] = SecretWord(rng.next()) + a.limbs[i] = SecretWord rng.random_word_highHammingWeight() + +func random_highHammingWeight[T](rng: var RngState, a: var T, C: static Curve) = + ## Recursively initialize a BigInt (part of a field) or Field element + ## Unsafe: for testing and benchmarking purposes only + ## The result will have a high Hamming Weight + ## to have a higher probability of triggering carries + when T is BigInt: + var reduced, unreduced{.noInit.}: T + rng.random_highHammingWeight(unreduced) + + # Note: a simple modulo will be biaised but it's simple and "fast" + reduced.reduce(unreduced, C.Mod) + a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul()) + + else: + for field in fields(a): + rng.random_highHammingWeight(field, C) + +func random_long01Seq(rng: var RngState, a: var openArray[byte]) = + ## Initialize a bytearray + ## It is skewed towards producing strings of 1111... and 0000 + ## to trigger edge cases + # See libsecp256k1: https://github.com/bitcoin-core/secp256k1/blob/dbd41db1/src/testrand_impl.h#L90-L104 + let Bits = a.len * 8 + var bit = 0 + zeroMem(a[0].addr, a.len) + while bit < Bits : + var now = 1 + (rng.random_unsafe(1 shl 6) * rng.random_unsafe(1 shl 5) + 16) div 31 + let val = rng.sample_unsafe([0, 1]) + while now > 0 and bit < Bits: + a[bit shr 3] = a[bit shr 3] or byte(val shl (bit and 7)) + dec now + inc bit + +func random_long01Seq(rng: var RngState, a: var BigInt) = + ## Initialize a bigint + ## It is skewed towards producing strings of 1111... and 0000 + ## to trigger edge cases + var buf: array[(a.bits + 7) div 8, byte] + rng.random_long01Seq(buf) + let order = rng.sample_unsafe([bigEndian, littleEndian]) + if order == bigEndian: + a.fromRawUint(buf, bigEndian) + else: + a.fromRawUint(buf, littleEndian) + +func random_long01Seq[T](rng: var RngState, a: var T, C: static Curve) = + ## Recursively initialize a BigInt (part of a field) or Field element + ## It is skewed towards producing strings of 1111... and 0000 + ## to trigger edge cases + when T is BigInt: + var reduced, unreduced{.noInit.}: T + rng.random_long01Seq(unreduced) + + # Note: a simple modulo will be biaised but it's simple and "fast" + reduced.reduce(unreduced, C.Mod) + a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul()) + + else: + for field in fields(a): + rng.random_highHammingWeight(field, C) # Elliptic curves # ------------------------------------------------------------ @@ -132,44 +251,65 @@ func random_unsafe_with_randZ[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = rng.random_unsafe(fieldElem, F.C) success = trySetFromCoordsXandZ(a, fieldElem, Z) -# Integer ranges +func random_highHammingWeight[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate == 1 + ## This will be generated with a biaised RNG with high Hamming Weight + ## to trigger carry bugs + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + # Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` + # so we have a probability of ~0.5 to get a good point + rng.random_highHammingWeight(fieldElem, F.C) + success = trySetFromCoordX(a, fieldElem) + +func random_highHammingWeight_with_randZ[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate == 1 + ## This will be generated with a biaised RNG with high Hamming Weight + ## to trigger carry bugs + var Z{.noInit.}: F + rng.random_highHammingWeight(Z, F.C) # If Z is zero, X will be zero and that will be an infinity point + + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + rng.random_highHammingWeight(fieldElem, F.C) + success = trySetFromCoordsXandZ(a, fieldElem, Z) + +func random_long01Seq[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate == 1 + ## This will be generated with a biaised RNG + ## that produces long bitstrings of 0 and 1 + ## to trigger edge cases + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + # Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` + # so we have a probability of ~0.5 to get a good point + rng.random_long01Seq(fieldElem, F.C) + success = trySetFromCoordX(a, fieldElem) + +func random_long01Seq_with_randZ[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate == 1 + ## This will be generated with a biaised RNG + ## that produces long bitstrings of 0 and 1 + ## to trigger edge cases + var Z{.noInit.}: F + rng.random_long01Seq(Z, F.C) # If Z is zero, X will be zero and that will be an infinity point + + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + rng.random_long01Seq(fieldElem, F.C) + success = trySetFromCoordsXandZ(a, fieldElem, Z) + +# Generic over any Constantine type # ------------------------------------------------------------ -func random_unsafe*(rng: var RngState, maxExclusive: uint32): uint32 = - ## Generate a random integer in 0 ..< maxExclusive - ## Uses an unbiaised generation method - ## See Lemire's algorithm modified by Melissa O'Neill - ## https://www.pcg-random.org/posts/bounded-rands.html - let max = maxExclusive - var x = uint32 rng.next() - var m = x.uint64 * max.uint64 - var l = uint32 m - if l < max: - var t = not(max) + 1 # -max - if t >= max: - t -= max - if t >= max: - t = t mod max - while l < t: - x = uint32 rng.next() - m = x.uint64 * max.uint64 - l = uint32 m - return uint32(m shr 32) - -# Generic over any supported type -# ------------------------------------------------------------ - -func sample_unsafe*[T](rng: var RngState, src: openarray[T]): T = - ## Return a random sample from an array - result = src[rng.random_unsafe(uint32 src.len)] - -func random_unsafe*[T: SomeInteger](rng: var RngState, inclRange: Slice[T]): T = - ## Return a random integer in the given range. - ## The range bounds must fit in an int32. - let maxExclusive = inclRange.b + 1 - inclRange.a - result = T(rng.random_unsafe(uint32 maxExclusive)) - result += inclRange.a - func random_unsafe*(rng: var RngState, T: typedesc): T = ## Create a random Field or Extension Field or Curve Element ## Unsafe: for testing and benchmarking purposes only @@ -187,11 +327,45 @@ func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T ## Unsafe: for testing and benchmarking purposes only rng.random_unsafe_with_randZ(result) +func random_highHammingWeight*(rng: var RngState, T: typedesc): T = + ## Create a random Field or Extension Field or Curve Element + ## Skewed towards high Hamming Weight + when T is ECP_SWei_Proj: + rng.random_highHammingWeight(result) + elif T is SomeNumber: + cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid) + elif T is BigInt: + rng.random_highHammingWeight(result) + else: # Fields + rng.random_highHammingWeight(result, T.C) + +func random_highHammingWeight_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T = + ## Create a random curve element with a random Z coordinate + ## Skewed towards high Hamming Weight + rng.random_highHammingWeight_with_randZ(result) + +func random_long01Seq*(rng: var RngState, T: typedesc): T = + ## Create a random Field or Extension Field or Curve Element + ## Skewed towards long bitstrings of 0 or 1 + when T is ECP_SWei_Proj: + rng.random_long01Seq(result) + elif T is SomeNumber: + cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid) + elif T is BigInt: + rng.random_long01Seq(result) + else: # Fields + rng.random_long01Seq(result, T.C) + +func random_long01Seq_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T = + ## Create a random curve element with a random Z coordinate + ## Skewed towards long bitstrings of 0 or 1 + rng.random_long01Seq_with_randZ(result) + # Sanity checks # ------------------------------------------------------------ when isMainModule: - import std/[tables, times] + import std/[tables, times, strutils] var rng: RngState let timeSeed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 @@ -211,3 +385,22 @@ when isMainModule: test(0..2) test(1..52) test(-10..10) + + echo "\n-----------------------------\n" + echo "High Hamming Weight check" + for _ in 0 ..< 10: + let word = rng.random_word_highHammingWeight() + echo "0b", cast[BiggestInt](word).toBin(WordBitWidth), " - 0x", word.toHex() + + echo "\n-----------------------------\n" + echo "Long strings of 0 or 1 check" + for _ in 0 ..< 10: + var a: BigInt[127] + rng.random_long01seq(a) + stdout.write "0b" + for word in a.limbs: + stdout.write cast[BiggestInt](word).toBin(WordBitWidth) + stdout.write " - 0x" + for word in a.limbs: + stdout.write word.BaseType.toHex() + stdout.write '\n' diff --git a/tests/t_ec_template.nim b/tests/t_ec_template.nim index 6c652b9..e78a66a 100644 --- a/tests/t_ec_template.nim +++ b/tests/t_ec_template.nim @@ -25,6 +25,28 @@ import ../helpers/prng_unsafe, ./support/ec_reference_scalar_mult +type + RandomGen = enum + Uniform + HighHammingWeight + Long01Sequence + +func random_point(rng: var RngState, F: typedesc, randZ: static bool, gen: static RandomGen): F {.inline, noInit.} = + when not randZ: + when gen == Uniform: + result = rng.random_unsafe(F) + elif gen == HighHammingWeight: + result = rng.random_highHammingWeight(F) + else: + result = rng.random_long01Seq(F) + else: + when gen == Uniform: + result = rng.random_unsafe_with_randZ(F) + elif gen == HighHammingWeight: + result = rng.random_highHammingWeight_with_randZ(F) + else: + result = rng.random_long01Seq_with_randZ(F) + proc run_EC_addition_tests*( ec: typedesc, Iters: static int, @@ -47,17 +69,14 @@ proc run_EC_addition_tests*( suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": test "The infinity point is the neutral element w.r.t. to EC " & G1_or_G2 & " addition": - proc test(EC: typedesc, randZ: static bool) = + proc test(EC: typedesc, randZ: static bool, gen: static RandomGen) = var inf {.noInit.}: EC inf.setInf() check: bool inf.isInf() for _ in 0 ..< Iters: var r{.noInit.}: EC - when randZ: - let P = rng.random_unsafe_with_randZ(EC) - else: - let P = rng.random_unsafe(EC) + let P = rng.random_point(EC, randZ, gen) r.sum(P, inf) check: bool(r == P) @@ -65,17 +84,18 @@ proc run_EC_addition_tests*( r.sum(inf, P) check: bool(r == P) - test(ec, randZ = false) - test(ec, randZ = true) + test(ec, randZ = false, gen = Uniform) + test(ec, randZ = true, gen = Uniform) + test(ec, randZ = false, gen = HighHammingWeight) + test(ec, randZ = true, gen = HighHammingWeight) + test(ec, randZ = false, gen = Long01Sequence) + test(ec, randZ = true, gen = Long01Sequence) test "Adding opposites gives an infinity point": - proc test(EC: typedesc, randZ: static bool) = + proc test(EC: typedesc, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< Iters: var r{.noInit.}: EC - when randZ: - let P = rng.random_unsafe_with_randZ(EC) - else: - let P = rng.random_unsafe(EC) + let P = rng.random_point(EC, randZ, gen) var Q = P Q.neg() @@ -85,38 +105,37 @@ proc run_EC_addition_tests*( r.sum(Q, P) check: bool r.isInf() - test(ec, randZ = false) - test(ec, randZ = true) + test(ec, randZ = false, gen = Uniform) + test(ec, randZ = true, gen = Uniform) + test(ec, randZ = false, gen = HighHammingWeight) + test(ec, randZ = true, gen = HighHammingWeight) + test(ec, randZ = false, gen = Long01Sequence) + test(ec, randZ = true, gen = Long01Sequence) test "EC " & G1_or_G2 & " add is commutative": - proc test(EC: typedesc, randZ: static bool) = + proc test(EC: typedesc, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< Iters: var r0{.noInit.}, r1{.noInit.}: EC - when randZ: - let P = rng.random_unsafe_with_randZ(EC) - let Q = rng.random_unsafe_with_randZ(EC) - else: - let P = rng.random_unsafe(EC) - let Q = rng.random_unsafe(EC) + let P = rng.random_point(EC, randZ, gen) + let Q = rng.random_point(EC, randZ, gen) r0.sum(P, Q) r1.sum(Q, P) check: bool(r0 == r1) - test(ec, randZ = false) - test(ec, randZ = true) + test(ec, randZ = false, gen = Uniform) + test(ec, randZ = true, gen = Uniform) + test(ec, randZ = false, gen = HighHammingWeight) + test(ec, randZ = true, gen = HighHammingWeight) + test(ec, randZ = false, gen = Long01Sequence) + test(ec, randZ = true, gen = Long01Sequence) test "EC " & G1_or_G2 & " add is associative": - proc test(EC: typedesc, randZ: static bool) = + proc test(EC: typedesc, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< Iters: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - let b = rng.random_unsafe_with_randZ(EC) - let c = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) - let b = rng.random_unsafe(EC) - let c = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) + let b = rng.random_point(EC, randZ, gen) + let c = rng.random_point(EC, randZ, gen) var tmp1{.noInit.}, tmp2{.noInit.}: EC @@ -153,16 +172,17 @@ proc run_EC_addition_tests*( bool(r0 == r3) bool(r0 == r4) - test(ec, randZ = false) - test(ec, randZ = true) + test(ec, randZ = false, gen = Uniform) + test(ec, randZ = true, gen = Uniform) + test(ec, randZ = false, gen = HighHammingWeight) + test(ec, randZ = true, gen = HighHammingWeight) + test(ec, randZ = false, gen = Long01Sequence) + test(ec, randZ = true, gen = Long01Sequence) test "EC " & G1_or_G2 & " double and EC " & G1_or_G2 & " add are consistent": - proc test(EC: typedesc, randZ: static bool) = + proc test(EC: typedesc, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< Iters: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) var r0{.noInit.}, r1{.noInit.}: EC @@ -171,8 +191,12 @@ proc run_EC_addition_tests*( check: bool(r0 == r1) - test(ec, randZ = false) - test(ec, randZ = true) + test(ec, randZ = false, gen = Uniform) + test(ec, randZ = true, gen = Uniform) + test(ec, randZ = false, gen = HighHammingWeight) + test(ec, randZ = true, gen = HighHammingWeight) + test(ec, randZ = false, gen = Long01Sequence) + test(ec, randZ = true, gen = Long01Sequence) proc run_EC_mul_sanity_tests*( ec: typedesc, @@ -196,12 +220,9 @@ proc run_EC_mul_sanity_tests*( suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": test "EC " & G1_or_G2 & " mul [0]P == Inf": - proc test(EC: typedesc, bits: static int, randZ: static bool) = + proc test(EC: typedesc, bits: static int, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< ItersMul: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) # zeroInit var exponentCanonical: array[(bits+7) div 8, byte] @@ -218,16 +239,17 @@ proc run_EC_mul_sanity_tests*( bool(impl.isInf()) bool(reference.isInf()) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Long01Sequence) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Long01Sequence) test "EC " & G1_or_G2 & " mul [1]P == P": - proc test(EC: typedesc, bits: static int, randZ: static bool) = + proc test(EC: typedesc, bits: static int, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< ItersMul: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) var exponent{.noInit.}: BigInt[bits] exponent.setOne() @@ -246,16 +268,17 @@ proc run_EC_mul_sanity_tests*( bool(impl == a) bool(reference == a) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Long01Sequence) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Long01Sequence) test "EC " & G1_or_G2 & " mul [2]P == P.double()": - proc test(EC: typedesc, bits: static int, randZ: static bool) = + proc test(EC: typedesc, bits: static int, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< ItersMul: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) var doubleA{.noInit.}: EC doubleA.double(a) @@ -276,8 +299,12 @@ proc run_EC_mul_sanity_tests*( bool(impl == doubleA) bool(reference == doubleA) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Long01Sequence) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Long01Sequence) proc run_EC_mul_distributive_tests*( ec: typedesc, @@ -302,14 +329,10 @@ proc run_EC_mul_distributive_tests*( suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": test "EC " & G1_or_G2 & " mul is distributive over EC add": - proc test(EC: typedesc, bits: static int, randZ: static bool) = + proc test(EC: typedesc, bits: static int, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< ItersMul: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - let b = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) - let b = rng.random_unsafe_with_randZ(EC) + let a = rng.random_point(EC, randZ, gen) + let b = rng.random_point(EC, randZ, gen) let exponent = rng.random_unsafe(BigInt[bits]) var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] @@ -349,8 +372,12 @@ proc run_EC_mul_distributive_tests*( bool(fReference == kakbRef) bool(fImpl == fReference) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Long01Sequence) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Long01Sequence) proc run_EC_mul_vs_ref_impl*( ec: typedesc, @@ -374,12 +401,9 @@ proc run_EC_mul_vs_ref_impl*( suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": test "EC " & G1_or_G2 & " mul constant-time is equivalent to a simple double-and-add algorithm": - proc test(EC: typedesc, bits: static int, randZ: static bool) = + proc test(EC: typedesc, bits: static int, randZ: static bool, gen: static RandomGen) = for _ in 0 ..< ItersMul: - when randZ: - let a = rng.random_unsafe_with_randZ(EC) - else: - let a = rng.random_unsafe(EC) + let a = rng.random_point(EC, randZ, gen) let exponent = rng.random_unsafe(BigInt[bits]) var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] @@ -395,5 +419,9 @@ proc run_EC_mul_vs_ref_impl*( check: bool(impl == reference) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) - test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Uniform) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = HighHammingWeight) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Long01Sequence) + test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true, gen = Long01Sequence) diff --git a/tests/t_finite_fields_mulsquare.nim b/tests/t_finite_fields_mulsquare.nim index f2c2800..b0f84d3 100644 --- a/tests/t_finite_fields_mulsquare.nim +++ b/tests/t_finite_fields_mulsquare.nim @@ -115,15 +115,47 @@ proc randomCurve(C: static Curve) = doAssert bool(r_mul == r_sqr) +proc randomHighHammingWeight(C: static Curve) = + let a = rng.random_highHammingWeight(Fp[C]) + + var r_mul, r_sqr: Fp[C] + + r_mul.prod(a, a) + r_sqr.square(a) + + doAssert bool(r_mul == r_sqr) + +proc random_long01Seq(C: static Curve) = + let a = rng.random_long01Seq(Fp[C]) + + var r_mul, r_sqr: Fp[C] + + r_mul.prod(a, a) + r_sqr.square(a) + + doAssert bool(r_mul == r_sqr) + suite "Random Modular Squaring is consistent with Modular Multiplication" & " [" & $WordBitwidth & "-bit mode]": test "Random squaring mod P-224 [FastSquaring = " & $P224.canUseNoCarryMontySquare & "]": for _ in 0 ..< Iters: randomCurve(P224) + for _ in 0 ..< Iters: + randomHighHammingWeight(P224) + for _ in 0 ..< Iters: + random_long01Seq(P224) test "Random squaring mod P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]": for _ in 0 ..< Iters: randomCurve(P256) + for _ in 0 ..< Iters: + randomHighHammingWeight(P256) + for _ in 0 ..< Iters: + random_long01Seq(P256) test "Random squaring mod BLS12_381 [FastSquaring = " & $BLS12_381.canUseNoCarryMontySquare & "]": for _ in 0 ..< Iters: randomCurve(BLS12_381) + for _ in 0 ..< Iters: + randomHighHammingWeight(BLS12_381) + for _ in 0 ..< Iters: + random_long01Seq(BLS12_381) diff --git a/tests/t_finite_fields_powinv.nim b/tests/t_finite_fields_powinv.nim index a0f7133..d31e1cf 100644 --- a/tests/t_finite_fields_powinv.nim +++ b/tests/t_finite_fields_powinv.nim @@ -170,6 +170,26 @@ proc main() = a2.double() check: bool(a == a2) + for _ in 0 ..< Iters: + let a = rng.randomHighHammingWeight(Fp[curve]) + var a2 = a + a2.double() + a2.div2() + check: bool(a == a2) + a2.div2() + a2.double() + check: bool(a == a2) + + for _ in 0 ..< Iters: + let a = rng.random_long01Seq(Fp[curve]) + var a2 = a + a2.double() + a2.div2() + check: bool(a == a2) + a2.div2() + a2.double() + check: bool(a == a2) + testRandomDiv2 P224 testRandomDiv2 BN254_Nogami testRandomDiv2 BN254_Snarks @@ -245,6 +265,22 @@ proc main() = r.prod(aInv, a) check: bool r.isOne() + for _ in 0 ..< Iters: + let a = rng.randomHighHammingWeight(Fp[curve]) + aInv.inv(a) + r.prod(a, aInv) + check: bool r.isOne() + r.prod(aInv, a) + check: bool r.isOne() + + for _ in 0 ..< Iters: + let a = rng.random_long01Seq(Fp[curve]) + aInv.inv(a) + r.prod(a, aInv) + check: bool r.isOne() + r.prod(aInv, a) + check: bool r.isOne() + testRandomInv P224 testRandomInv BN254_Nogami testRandomInv BN254_Snarks diff --git a/tests/t_finite_fields_sqrt.nim b/tests/t_finite_fields_sqrt.nim index 498721d..c427729 100644 --- a/tests/t_finite_fields_sqrt.nim +++ b/tests/t_finite_fields_sqrt.nim @@ -83,27 +83,38 @@ proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) = bool (a == a2) # a shouldn't be modified proc randomSqrtCheck_p3mod4(C: static Curve) = + template testImpl(a: untyped): untyped {.dirty.} = + var na{.noInit.}: Fp[C] + na.neg(a) + + var a2 = a + var na2 = na + a2.square() + na2.square() + check: + bool a2 == na2 + bool a2.isSquare() + + var r, s = a2 + r.sqrt() + let ok = s.sqrt_if_square() + check: + bool ok + bool(r == s) + bool(r == a or r == na) + test "Random square root check for p ≡ 3 (mod 4) on " & $Curve(C): for _ in 0 ..< Iters: let a = rng.random_unsafe(Fp[C]) - var na{.noInit.}: Fp[C] - na.neg(a) + testImpl(a) - var a2 = a - var na2 = na - a2.square() - na2.square() - check: - bool a2 == na2 - bool a2.isSquare() + for _ in 0 ..< Iters: + let a = rng.randomHighHammingWeight(Fp[C]) + testImpl(a) - var r, s = a2 - r.sqrt() - let ok = s.sqrt_if_square() - check: - bool ok - bool(r == s) - bool(r == a or r == na) + for _ in 0 ..< Iters: + let a = rng.random_long01Seq(Fp[C]) + testImpl(a) proc main() = suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]": diff --git a/tests/t_fp_tower_template.nim b/tests/t_fp_tower_template.nim index d41af7f..c51947f 100644 --- a/tests/t_fp_tower_template.nim +++ b/tests/t_fp_tower_template.nim @@ -35,6 +35,20 @@ template ExtField(degree: static int, curve: static Curve): untyped = else: {.error: "Unconfigured extension degree".} +type + RandomGen = enum + Uniform + HighHammingWeight + Long01Sequence + +func random_elem(rng: var RngState, F: typedesc, gen: static RandomGen): F {.inline, noInit.} = + when gen == Uniform: + result = rng.random_unsafe(F) + elif gen == HighHammingWeight: + result = rng.random_highHammingWeight(F) + else: + result = rng.random_long01Seq(F) + proc runTowerTests*[N]( ExtDegree: static int, Iters: static int, @@ -63,19 +77,19 @@ proc runTowerTests*[N]( test(ExtField(ExtDegree, curve)) test "Addition, substraction negation are consistent": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = # 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 for _ in 0 ..< Iters: One.setOne() - a = rng.random_unsafe(Field) + a = rng.random_elem(Field, gen) a2 = a a2.double() na.neg(a) - b = rng.random_unsafe(Field) + b = rng.random_elem(Field, gen) b2.double(b) nb.neg(b) @@ -92,12 +106,14 @@ proc runTowerTests*[N]( check: bool accum.isOne() staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Division by 2": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) var a2 = a a2.double() a2.div2() @@ -107,7 +123,9 @@ proc runTowerTests*[N]( check: bool(a == a2) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Squaring 1 returns 1": proc test(Field: typedesc) = @@ -247,9 +265,9 @@ proc runTowerTests*[N]( check: bool(r == x) test "Multiplication and Squaring are consistent": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) var rMul{.noInit.}, rSqr{.noInit.}: Field rMul.prod(a, a) @@ -258,12 +276,14 @@ proc runTowerTests*[N]( check: bool(rMul == rSqr) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Squaring the opposite gives the same result": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) var na{.noInit.}: Field na.neg(a) @@ -275,14 +295,16 @@ proc runTowerTests*[N]( check: bool(rSqr == rNegSqr) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Multiplication and Addition/Substraction are consistent": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: let factor = rng.random_unsafe(-30..30) - let a = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) if factor == 0: continue @@ -309,14 +331,16 @@ proc runTowerTests*[N]( check: bool(r == sum) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Addition is associative and commutative": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) - let b = rng.random_unsafe(Field) - let c = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) + let b = rng.random_elem(Field, gen) + let c = rng.random_elem(Field, gen) var tmp1{.noInit.}, tmp2{.noInit.}: Field @@ -354,14 +378,16 @@ proc runTowerTests*[N]( bool(r0 == r4) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Multiplication is associative and commutative": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) - let b = rng.random_unsafe(Field) - let c = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) + let b = rng.random_elem(Field, gen) + let c = rng.random_elem(Field, gen) var tmp1{.noInit.}, tmp2{.noInit.}: Field @@ -399,14 +425,16 @@ proc runTowerTests*[N]( bool(r0 == r4) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "Extension field multiplicative inverse": - proc test(Field: typedesc, Iters: static int) = + proc test(Field: typedesc, Iters: static int, gen: static RandomGen) = var aInv, r{.noInit.}: Field for _ in 0 ..< Iters: - let a = rng.random_unsafe(Field) + let a = rng.random_elem(Field, gen) aInv.inv(a) r.prod(a, aInv) check: bool(r.isOne()) @@ -414,7 +442,9 @@ proc runTowerTests*[N]( check: bool(r.isOne()) staticFor(curve, TestCurves): - test(ExtField(ExtDegree, curve), Iters) + test(ExtField(ExtDegree, curve), Iters, gen = Uniform) + test(ExtField(ExtDegree, curve), Iters, gen = HighHammingWeight) + test(ExtField(ExtDegree, curve), Iters, gen = Long01Sequence) test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion": proc test(Field: typedesc) =