[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
This commit is contained in:
Mamy Ratsimbazafy 2020-06-20 18:55:27 +02:00 committed by GitHub
parent a2a2495351
commit e491f3b91d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 496 additions and 166 deletions

View File

@ -8,8 +8,10 @@
import import
../constantine/arithmetic/bigints, ../constantine/arithmetic/bigints,
../constantine/primitives,
../constantine/config/[common, curves], ../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); 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 # 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) = func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) =
## Recursively initialize a BigInt (part of a field) or Field element ## Recursively initialize a BigInt (part of a field) or Field element
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
when T is BigInt: when T is BigInt:
var reduced, unreduced{.noInit.}: T var reduced, unreduced{.noInit.}: T
rng.random_unsafe(unreduced)
for i in 0 ..< unreduced.limbs.len:
unreduced.limbs[i] = SecretWord(rng.next())
# Note: a simple modulo will be biaised but it's simple and "fast" # Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, C.Mod) 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): for field in fields(a):
rng.random_unsafe(field, C) 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 ## Initialize a standalone BigInt
## with high Hamming weight
## to have a higher probability of triggering carries
for i in 0 ..< a.limbs.len: 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 # 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) rng.random_unsafe(fieldElem, F.C)
success = trySetFromCoordsXandZ(a, fieldElem, Z) 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 = func random_unsafe*(rng: var RngState, T: typedesc): T =
## Create a random Field or Extension Field or Curve Element ## Create a random Field or Extension Field or Curve Element
## Unsafe: for testing and benchmarking purposes only ## 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 ## Unsafe: for testing and benchmarking purposes only
rng.random_unsafe_with_randZ(result) 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 # Sanity checks
# ------------------------------------------------------------ # ------------------------------------------------------------
when isMainModule: when isMainModule:
import std/[tables, times] import std/[tables, times, strutils]
var rng: RngState var rng: RngState
let timeSeed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 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(0..2)
test(1..52) test(1..52)
test(-10..10) 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'

View File

@ -25,6 +25,28 @@ import
../helpers/prng_unsafe, ../helpers/prng_unsafe,
./support/ec_reference_scalar_mult ./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*( proc run_EC_addition_tests*(
ec: typedesc, ec: typedesc,
Iters: static int, Iters: static int,
@ -47,17 +69,14 @@ proc run_EC_addition_tests*(
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "The infinity point is the neutral element w.r.t. to EC " & G1_or_G2 & " addition": 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 var inf {.noInit.}: EC
inf.setInf() inf.setInf()
check: bool inf.isInf() check: bool inf.isInf()
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
var r{.noInit.}: EC var r{.noInit.}: EC
when randZ: let P = rng.random_point(EC, randZ, gen)
let P = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
r.sum(P, inf) r.sum(P, inf)
check: bool(r == P) check: bool(r == P)
@ -65,17 +84,18 @@ proc run_EC_addition_tests*(
r.sum(inf, P) r.sum(inf, P)
check: bool(r == P) check: bool(r == P)
test(ec, randZ = false) test(ec, randZ = false, gen = Uniform)
test(ec, randZ = true) 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": 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: for _ in 0 ..< Iters:
var r{.noInit.}: EC var r{.noInit.}: EC
when randZ: let P = rng.random_point(EC, randZ, gen)
let P = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
var Q = P var Q = P
Q.neg() Q.neg()
@ -85,38 +105,37 @@ proc run_EC_addition_tests*(
r.sum(Q, P) r.sum(Q, P)
check: bool r.isInf() check: bool r.isInf()
test(ec, randZ = false) test(ec, randZ = false, gen = Uniform)
test(ec, randZ = true) 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": 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: for _ in 0 ..< Iters:
var r0{.noInit.}, r1{.noInit.}: EC var r0{.noInit.}, r1{.noInit.}: EC
when randZ: let P = rng.random_point(EC, randZ, gen)
let P = rng.random_unsafe_with_randZ(EC) let Q = rng.random_point(EC, randZ, gen)
let Q = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
let Q = rng.random_unsafe(EC)
r0.sum(P, Q) r0.sum(P, Q)
r1.sum(Q, P) r1.sum(Q, P)
check: bool(r0 == r1) check: bool(r0 == r1)
test(ec, randZ = false) test(ec, randZ = false, gen = Uniform)
test(ec, randZ = true) 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": 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: for _ in 0 ..< Iters:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC) let b = rng.random_point(EC, randZ, gen)
let b = rng.random_unsafe_with_randZ(EC) let c = rng.random_point(EC, randZ, gen)
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)
var tmp1{.noInit.}, tmp2{.noInit.}: EC var tmp1{.noInit.}, tmp2{.noInit.}: EC
@ -153,16 +172,17 @@ proc run_EC_addition_tests*(
bool(r0 == r3) bool(r0 == r3)
bool(r0 == r4) bool(r0 == r4)
test(ec, randZ = false) test(ec, randZ = false, gen = Uniform)
test(ec, randZ = true) 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": 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: for _ in 0 ..< Iters:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
var r0{.noInit.}, r1{.noInit.}: EC var r0{.noInit.}, r1{.noInit.}: EC
@ -171,8 +191,12 @@ proc run_EC_addition_tests*(
check: bool(r0 == r1) check: bool(r0 == r1)
test(ec, randZ = false) test(ec, randZ = false, gen = Uniform)
test(ec, randZ = true) 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*( proc run_EC_mul_sanity_tests*(
ec: typedesc, ec: typedesc,
@ -196,12 +220,9 @@ proc run_EC_mul_sanity_tests*(
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul [0]P == Inf": 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: for _ in 0 ..< ItersMul:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
# zeroInit # zeroInit
var exponentCanonical: array[(bits+7) div 8, byte] var exponentCanonical: array[(bits+7) div 8, byte]
@ -218,16 +239,17 @@ proc run_EC_mul_sanity_tests*(
bool(impl.isInf()) bool(impl.isInf())
bool(reference.isInf()) bool(reference.isInf())
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) 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": 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: for _ in 0 ..< ItersMul:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
var exponent{.noInit.}: BigInt[bits] var exponent{.noInit.}: BigInt[bits]
exponent.setOne() exponent.setOne()
@ -246,16 +268,17 @@ proc run_EC_mul_sanity_tests*(
bool(impl == a) bool(impl == a)
bool(reference == a) bool(reference == a)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) 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()": 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: for _ in 0 ..< ItersMul:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
var doubleA{.noInit.}: EC var doubleA{.noInit.}: EC
doubleA.double(a) doubleA.double(a)
@ -276,8 +299,12 @@ proc run_EC_mul_sanity_tests*(
bool(impl == doubleA) bool(impl == doubleA)
bool(reference == doubleA) bool(reference == doubleA)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) 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*( proc run_EC_mul_distributive_tests*(
ec: typedesc, ec: typedesc,
@ -302,14 +329,10 @@ proc run_EC_mul_distributive_tests*(
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul is distributive over EC add": 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: for _ in 0 ..< ItersMul:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC) let b = rng.random_point(EC, randZ, gen)
let b = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let b = rng.random_unsafe_with_randZ(EC)
let exponent = rng.random_unsafe(BigInt[bits]) let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
@ -349,8 +372,12 @@ proc run_EC_mul_distributive_tests*(
bool(fReference == kakbRef) bool(fReference == kakbRef)
bool(fImpl == fReference) bool(fImpl == fReference)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) 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*( proc run_EC_mul_vs_ref_impl*(
ec: typedesc, ec: typedesc,
@ -374,12 +401,9 @@ proc run_EC_mul_vs_ref_impl*(
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul constant-time is equivalent to a simple double-and-add algorithm": 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: for _ in 0 ..< ItersMul:
when randZ: let a = rng.random_point(EC, randZ, gen)
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let exponent = rng.random_unsafe(BigInt[bits]) let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
@ -395,5 +419,9 @@ proc run_EC_mul_vs_ref_impl*(
check: bool(impl == reference) check: bool(impl == reference)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false) test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false, gen = Uniform)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true) 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)

View File

@ -115,15 +115,47 @@ proc randomCurve(C: static Curve) =
doAssert bool(r_mul == r_sqr) 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]": suite "Random Modular Squaring is consistent with Modular Multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Random squaring mod P-224 [FastSquaring = " & $P224.canUseNoCarryMontySquare & "]": test "Random squaring mod P-224 [FastSquaring = " & $P224.canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
randomCurve(P224) randomCurve(P224)
for _ in 0 ..< Iters:
randomHighHammingWeight(P224)
for _ in 0 ..< Iters:
random_long01Seq(P224)
test "Random squaring mod P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]": test "Random squaring mod P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
randomCurve(P256) 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 & "]": test "Random squaring mod BLS12_381 [FastSquaring = " & $BLS12_381.canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
randomCurve(BLS12_381) randomCurve(BLS12_381)
for _ in 0 ..< Iters:
randomHighHammingWeight(BLS12_381)
for _ in 0 ..< Iters:
random_long01Seq(BLS12_381)

View File

@ -170,6 +170,26 @@ proc main() =
a2.double() a2.double()
check: bool(a == a2) 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 P224
testRandomDiv2 BN254_Nogami testRandomDiv2 BN254_Nogami
testRandomDiv2 BN254_Snarks testRandomDiv2 BN254_Snarks
@ -245,6 +265,22 @@ proc main() =
r.prod(aInv, a) r.prod(aInv, a)
check: bool r.isOne() 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 P224
testRandomInv BN254_Nogami testRandomInv BN254_Nogami
testRandomInv BN254_Snarks testRandomInv BN254_Snarks

View File

@ -83,9 +83,7 @@ proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) =
bool (a == a2) # a shouldn't be modified bool (a == a2) # a shouldn't be modified
proc randomSqrtCheck_p3mod4(C: static Curve) = proc randomSqrtCheck_p3mod4(C: static Curve) =
test "Random square root check for p ≡ 3 (mod 4) on " & $Curve(C): template testImpl(a: untyped): untyped {.dirty.} =
for _ in 0 ..< Iters:
let a = rng.random_unsafe(Fp[C])
var na{.noInit.}: Fp[C] var na{.noInit.}: Fp[C]
na.neg(a) na.neg(a)
@ -105,6 +103,19 @@ proc randomSqrtCheck_p3mod4(C: static Curve) =
bool(r == s) bool(r == s)
bool(r == a or r == na) 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])
testImpl(a)
for _ in 0 ..< Iters:
let a = rng.randomHighHammingWeight(Fp[C])
testImpl(a)
for _ in 0 ..< Iters:
let a = rng.random_long01Seq(Fp[C])
testImpl(a)
proc main() = proc main() =
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]": suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
exhaustiveCheck_p3mod4 Fake103, 103 exhaustiveCheck_p3mod4 Fake103, 103

View File

@ -35,6 +35,20 @@ template ExtField(degree: static int, curve: static Curve): untyped =
else: else:
{.error: "Unconfigured extension degree".} {.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]( proc runTowerTests*[N](
ExtDegree: static int, ExtDegree: static int,
Iters: static int, Iters: static int,
@ -63,19 +77,19 @@ proc runTowerTests*[N](
test(ExtField(ExtDegree, curve)) test(ExtField(ExtDegree, curve))
test "Addition, substraction negation are consistent": 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 # 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 # (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 var accum {.noInit.}, One {.noInit.}, a{.noInit.}, na{.noInit.}, b{.noInit.}, nb{.noInit.}, a2 {.noInit.}, b2 {.noInit.}: Field
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
One.setOne() One.setOne()
a = rng.random_unsafe(Field) a = rng.random_elem(Field, gen)
a2 = a a2 = a
a2.double() a2.double()
na.neg(a) na.neg(a)
b = rng.random_unsafe(Field) b = rng.random_elem(Field, gen)
b2.double(b) b2.double(b)
nb.neg(b) nb.neg(b)
@ -92,12 +106,14 @@ proc runTowerTests*[N](
check: bool accum.isOne() check: bool accum.isOne()
staticFor(curve, TestCurves): 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": 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: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
var a2 = a var a2 = a
a2.double() a2.double()
a2.div2() a2.div2()
@ -107,7 +123,9 @@ proc runTowerTests*[N](
check: bool(a == a2) check: bool(a == a2)
staticFor(curve, TestCurves): 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": test "Squaring 1 returns 1":
proc test(Field: typedesc) = proc test(Field: typedesc) =
@ -247,9 +265,9 @@ proc runTowerTests*[N](
check: bool(r == x) check: bool(r == x)
test "Multiplication and Squaring are consistent": 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: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
var rMul{.noInit.}, rSqr{.noInit.}: Field var rMul{.noInit.}, rSqr{.noInit.}: Field
rMul.prod(a, a) rMul.prod(a, a)
@ -258,12 +276,14 @@ proc runTowerTests*[N](
check: bool(rMul == rSqr) check: bool(rMul == rSqr)
staticFor(curve, TestCurves): 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": 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: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
var na{.noInit.}: Field var na{.noInit.}: Field
na.neg(a) na.neg(a)
@ -275,14 +295,16 @@ proc runTowerTests*[N](
check: bool(rSqr == rNegSqr) check: bool(rSqr == rNegSqr)
staticFor(curve, TestCurves): 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": 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: for _ in 0 ..< Iters:
let factor = rng.random_unsafe(-30..30) let factor = rng.random_unsafe(-30..30)
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
if factor == 0: continue if factor == 0: continue
@ -309,14 +331,16 @@ proc runTowerTests*[N](
check: bool(r == sum) check: bool(r == sum)
staticFor(curve, TestCurves): 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": 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: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
let b = rng.random_unsafe(Field) let b = rng.random_elem(Field, gen)
let c = rng.random_unsafe(Field) let c = rng.random_elem(Field, gen)
var tmp1{.noInit.}, tmp2{.noInit.}: Field var tmp1{.noInit.}, tmp2{.noInit.}: Field
@ -354,14 +378,16 @@ proc runTowerTests*[N](
bool(r0 == r4) bool(r0 == r4)
staticFor(curve, TestCurves): 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": 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: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
let b = rng.random_unsafe(Field) let b = rng.random_elem(Field, gen)
let c = rng.random_unsafe(Field) let c = rng.random_elem(Field, gen)
var tmp1{.noInit.}, tmp2{.noInit.}: Field var tmp1{.noInit.}, tmp2{.noInit.}: Field
@ -399,14 +425,16 @@ proc runTowerTests*[N](
bool(r0 == r4) bool(r0 == r4)
staticFor(curve, TestCurves): 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": 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 var aInv, r{.noInit.}: Field
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Field) let a = rng.random_elem(Field, gen)
aInv.inv(a) aInv.inv(a)
r.prod(a, aInv) r.prod(a, aInv)
check: bool(r.isOne()) check: bool(r.isOne())
@ -414,7 +442,9 @@ proc runTowerTests*[N](
check: bool(r.isOne()) check: bool(r.isOne())
staticFor(curve, TestCurves): 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": test "0 does not have a multiplicative inverse and should return 0 for projective/jacobian => affine coordinates conversion":
proc test(Field: typedesc) = proc test(Field: typedesc) =