Fuzzing campaign fixes (#58)

* Add test case for #30 - Euler's criterion doesn't return 1 for a square

* Detect #42 in the test suite

* Detect #43 in the test suite

* comment in sqrt tests

* Add #67 to the anti-regression suite

* Add #61 to the anti-regression suite

* Add #62 to anti-regression suite

* Add #60 to the anti-regression suite

* Add #64 to the test suite

* Add #65 - case 1

* Add #65 case 2

* Add #65 case 3

* Add debug check to isSquare/Euler's Criterion/Legendre Symbol

* Make sure our primitives are correct

* For now deactivate montySquare CIOS fix #61 #62

* Narrow down #42 and #43 to powinv on 32-bit

* Detect #42 #43 at the fast squaring level

* More #42, #43 tests, Use multiplication instead of squaring as a temporary workaround, see https://github.com/mratsim/constantine/issues/68

* Prevent regression of #67 now that squaring is "fixed"
This commit is contained in:
Mamy Ratsimbazafy 2020-06-23 01:27:40 +02:00 committed by GitHub
parent 0400187f05
commit ec76ac5ea6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 564 additions and 67 deletions

View File

@ -18,6 +18,7 @@ const buildParallel = "test_parallel.txt"
const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# Primitives
("tests/t_primitives.nim", false),
("tests/t_primitives_extended_precision.nim", false),
# Big ints
("tests/t_io_bigints.nim", false),
("tests/t_bigints.nim", false),
@ -60,7 +61,9 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_381.nim", false),
# Elliptic curve arithmetic vs Sagemath
("tests/t_ec_sage_bn254.nim", false),
("tests/t_ec_sage_bls12_381.nim", false)
("tests/t_ec_sage_bls12_381.nim", false),
# Edge cases highlighted by past bugs
("tests/t_ec_wstrass_prj_edge_cases.nim", false)
]
# For temporary (hopefully) investigation that can only be reproduced in CI

View File

@ -265,6 +265,12 @@ func isSquare*[C](a: Fp[C]): SecretBool =
# - 1 if a square
# - 0 if 0
# - -1 if a quadratic non-residue
debug:
doAssert: bool(
xi.isZero or
xi.isOne or
xi.mres == C.getMontyPrimeMinus1()
)
func sqrt_p3mod4[C](a: var Fp[C]) =
## Compute the square root of ``a``

View File

@ -212,6 +212,12 @@ func montySquare_CIOS_nocarry(r: var Limbs, a, M: Limbs, m0ninv: BaseType) =
## M[^1] < high(SecretWord) shr 2 (i.e. less than 0b00111...1111)
## https://hackmd.io/@zkteam/modular_multiplication
# TODO: Deactivated
# Off-by one on 32-bit on the least significant bit
# for Fp[BLS12-381] with inputs
# - -0x091F02EFA1C9B99C004329E94CD3C6B308164CBE02037333D78B6C10415286F7C51B5CD7F917F77B25667AB083314B1B
# - -0x0B7C8AFE5D43E9A973AF8649AD8C733B97D06A78CFACD214CBE9946663C3F682362E0605BC8318714305B249B505AFD9
# We want all the computation to be kept in registers
# hence we use a temporary `t`, hoping that the compiler does it.
var t: typeof(M) # zero-init
@ -254,6 +260,14 @@ func montySquare_CIOS(r: var Limbs, a, M: Limbs, m0ninv: BaseType) =
## Koc, Acar, Kaliski, 1996
## https://www.semanticscholar.org/paper/Analyzing-and-comparing-Montgomery-multiplication-Ko%C3%A7-Acar/5e3941ff482ec3ee41dc53c3298f0be085c69483
# TODO: Deactivated
# Off-by one on 32-bit on the least significant bit
# for Fp[2^127 - 1] with inputs
# - -0x75bfffefbfffffff7fd9dfd800000000
# - -0x7ff7ffffffffffff1dfb7fafc0000000
# Squaring the number and its opposite
# should give the same result, but those are off-by-one
# We want all the computation to be kept in registers
# hence we use a temporary `t`, hoping that the compiler does it.
var t: typeof(M) # zero-init
@ -264,9 +278,8 @@ func montySquare_CIOS(r: var Limbs, a, M: Limbs, m0ninv: BaseType) =
staticFor i, 0, N:
# Squaring
var
A1: Carry
A0: SecretWord
var A1 = Carry(0)
var A0: SecretWord
# (A0, t[i]) <- a[i] * a[i] + t[i]
muladd1(A0, t[i], a[i], a[i], t[i])
staticFor j, i+1, N:
@ -340,9 +353,24 @@ func montySquare*(r: var Limbs, a, M: Limbs,
## `m0ninv` = -1/M (mod SecretWord). Our words are 2^31 or 2^63
when canUseNoCarryMontySquare:
montySquare_CIOS_nocarry(r, a, M, m0ninv)
# TODO: Deactivated
# Off-by one on 32-bit on the least significant bit
# for Fp[BLS12-381] with inputs
# - -0x091F02EFA1C9B99C004329E94CD3C6B308164CBE02037333D78B6C10415286F7C51B5CD7F917F77B25667AB083314B1B
# - -0x0B7C8AFE5D43E9A973AF8649AD8C733B97D06A78CFACD214CBE9946663C3F682362E0605BC8318714305B249B505AFD9
# montySquare_CIOS_nocarry(r, a, M, m0ninv)
montyMul_CIOS_nocarry(r, a, a, M, m0ninv)
else:
montySquare_CIOS(r, a, M, m0ninv)
# TODO: Deactivated
# Off-by one on 32-bit for Fp[2^127 - 1] with inputs
# - -0x75bfffefbfffffff7fd9dfd800000000
# - -0x7ff7ffffffffffff1dfb7fafc0000000
# Squaring the number and its opposite
# should give the same result, but those are off-by-one
# montySquare_CIOS(r, a, M, m0ninv) # TODO <--- Fix this
montyMul_FIPS(r, a, a, M, m0ninv)
func redc*(r: var Limbs, a, one, M: Limbs,
m0ninv: static BaseType, canUseNoCarryMontyMul: static bool) =

View File

@ -0,0 +1,177 @@
# 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.
# ############################################################
#
# Edge cases highlighted by property-based testing or fuzzing
#
# ############################################################
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/[io_bigints, io_fields, io_towers, io_ec],
../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult
func testAddAssociativity[EC](a, b, c: EC) =
var tmp1{.noInit.}, tmp2{.noInit.}: ECP_SWei_Proj[Fp2[BLS12_381]]
# 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
# ...
doAssert bool(r0 == r1)
doAssert bool(r0 == r2)
doAssert bool(r0 == r3)
doAssert bool(r0 == r4)
suite "Short Weierstrass Elliptic Curve - Edge cases [" & $WordBitwidth & "-bit mode]":
test "EC Add G2 is associative - #60":
var a, b, c: ECP_SWei_Proj[Fp2[BLS12_381]]
var ax, az, bx, bz, cx, cz: Fp2[BLS12_381]
ax.fromHex(
c0 = "0x0e98970ade3ffe2211cb555a47d889ed53a744dc35da27f5bd25d6a4c0931bb32925d8d376afa220afd9202b089e7721",
c1 = "0x0509eff595efe2d47afecaf025930d2be1f28b55be87abdf1a81676cd233b9adf98a172827ea4b52f295919710e80014"
)
az.fromHex(
c0 = "0x0f3935f4be148bb9c291f4562ac54363e3a82b3fd52dbdcb2281231ddfa3af6a898d48cfdf7e60a718d3b5061d384112",
c1 = "0x159b8b4aa0a1f09e9beecc5a77340566aeb3160cb62963cf162205fe7f2073956eba23a6381758ff1339b4fc95266d66"
)
bx.fromHex(
c0 = "0x06f7acb144c05d35e73c7af216980b058ddb38a241588c7a480292f8be9f9b1312ab0146744dda43b8f366ff6481780b",
c1 = "0x0a92a7c2328a3c9b787a6b7a015f692f6163af7314d1296721b88b4e1d605c8525997872c4288c0a404fd0fc645c0928"
)
bz.fromHex(
c0 = "0x0536c3f8eab95080c88e5963773cd164c6afe1d12064dc1a7f89cb03714d78b4e9308449f41aa5ef4d2823d59d0eeb34",
c1 = "0x0ab1c28bf9856db8770c799f2d9d5aec65d09bbe12f4fe28d896dc651492553d96baab853b72c705da2f7995d0ed5cea"
)
cx.fromHex(
c0 = "0x0ec13a3c32697133a43be9efc46d49e2aaef6d690c1d5645a1bc3aeca8abab0dfa63e3ef89ac1bea9ea82cabbdb5470f",
c1 = "0x0df8aa37e1828b29c3a21ebf9b72fcc2a0d9f67b62a1c4592161cbc1a849ad5c6991af2a7906609ab5bce4297bc2e312"
)
cz.fromHex(
c0 = "0x05177ec517616c9f154c0861dbc205638396b8af61004bed5166a4dc0ed0c79afa1eb1eef595b3ad925b9a277bbcb9fb",
c1 = "0x0cf0d2573e26463ab3117a4d27862077a22b2c3e9eeda3098bfa82d1be2bd2149b5b703a8192fdb9d9cc1c0dd3edde54"
)
doAssert bool a.trySetFromCoordsXandZ(ax, az)
doAssert bool b.trySetFromCoordsXandZ(bx, bz)
doAssert bool c.trySetFromCoordsXandZ(cx, cz)
testAddAssociativity(a, b, c)
test "EC Add G2 is associative - #65-1":
var a, b, c: ECP_SWei_Proj[Fp2[BLS12_381]]
var ax, az, bx, bz, cx, cz: Fp2[BLS12_381]
ax.fromHex(
c0 = "0x13d97382a3e097623d191172ec2972f3a4b436e24ae18f8394c9103a37c43b2747d5f7c597eff7bda406000000017ffd",
c1 = "0x11eca90d537eabf01ead08dce5d4f63822941ce7255cc7bfc62483dceb5d148f23f7bfcaeb7f5ffccd767ff5ffffdffe"
)
az.fromHex(
c0 = "0x15f65ec3fa7ce4935c071a97a256ec6d77ce385370513744df48944613b748b2a8e3bfdb035bfb7a7608ffc00002ff7c",
c1 = "0x15f646c3fa80e4835bd70a57a196ac6d57ce1653705247455f48983753c758bae9f3800ba3ebeff024c8cbd78002fdfc"
)
bx.fromHex(
c0 = "0x146e5ab3ea40d392d3868086a256ec2d524ce85345c237434ec0904f52d753b1ebf4000bc40c00026607fc000002fffc",
c1 = "0x15f65ebfb267a4935007168f6256ec6d75c11633705252c55f489857437e08a2ebf3b7a7c40c000275e7fff9f0025ffa"
)
bz.fromHex(
c0 = "0x0da4dec3fa76cb905c071a13a1d2c39906ce502d70085744df48985140be37fa6bd1ffdac407fff27608dfffde60fedc",
c1 = "0x0df55883b636e29344071a7aa255dc6d25a258126bbe0a455b48985753c4377aeaf3a3f6c40c00027307ffb7ffbdefdc"
)
cx.fromHex(
c0 = "0x11fcc7014aee3c2f1ead04bd25d8996fd29a1d71002e97bdca6d881d13ad1d937ff6ee83c8025feed202fffffbdcfffe",
c1 = "0x09ee82982d80b1c7bf3e69b228ee461c30bce73d574478841da0bd7941294503292b7809222bfe7d4606f976400244d2"
)
cz.fromHex(
c0 = "0x09ee82982d80b1c7bf3e69b228ee461c30bce73d574478841da0bd7941294503292b7809222bfe7d4606f976400244d2",
c1 = "0x15f35eab6e70e2922b85d257a256ec6d43794851f05257452de3965753474ca66bf3f923c10bfe022d07d7f60000fffb"
)
doAssert bool a.trySetFromCoordsXandZ(ax, az)
doAssert bool b.trySetFromCoordsXandZ(bx, bz)
doAssert bool c.trySetFromCoordsXandZ(cx, cz)
testAddAssociativity(a, b, c)
test "EC Add G2 is associative - #65-2":
var a, b, c: ECP_SWei_Proj[Fp2[BLS12_381]]
var ax, az, bx, bz, cx, cz: Fp2[BLS12_381]
ax.fromHex(
c0 = "0x0be65dc3f260e3814b86f997a256dc6cf5cbfc536ed257455f48985751c758b6d3efc005c38b00027588befff802fffc",
c1 = "0x015802786d80b1c7e206290223e4440c40a8da49575c7cc40ca93b99392944fd084ba00124b2fdfde907000000025552"
)
az.fromHex(
c0 = "0x13f1dcf37a53c48a5c071a972236ea6cebce5843674a5324542885d7098337b0e2ebe003b80bd801f588ffb7f55efbdb",
c1 = "0x05b5dec1fa80e4935c05fa869055ec6cb5b64fc37051d74557088c4753c758baeb31fd03420ae00155fe7e000002fffb"
)
bx.fromHex(
c0 = "0x0beb9e43fa1f34933c06ea5c9206536d67ce585330525744fe485756817f46ba53f3f00bc40c00027188ffeefbf2efe7",
c1 = "0x15f65ebdf640e4525c051a976256ec6d778c185370524f3d5f48905741c6d829ebf3ff6ba34abfb87607fed3cfaabfa8"
)
bz.fromHex(
c0 = "0x16fbb84711c0596bd3916126d2d0caa1da00b1bc116b70ff4938b574243aa76f754d5f05309fffa90ffbeff9e900b043",
c1 = "0x13d2848256ff557fbd1601aa27b8f07384e7faca4ae18d030c55883a36d63b1f4778000757ff780163f57ffffffee469"
)
cx.fromHex(
c0 = "0x15d0dd8bf97fe1eb37fe9a827a56e9665ace4bd168120cbd5b208e56f18f547aeaf2000b2289effa61fff7300002f7b9",
c1 = "0x15f65ec3fa80e4832ec68a97a256ec6d734e27cee05257435ef898554cc748bae3cfda0b998277c27606bffdf202ff7c"
)
cz.fromHex(
c0 = "0x05f61d97f970e1867be71a17a1d6e46d764e53ce7051d5455f4697d7139f54b8eb63f80bc40bfffe6e04fbffb5d2efba",
c1 = "0x15f65ec3f63fe0115b9ee2871232dc63378e584b6fc95742d807184cbb4735faebf4000ac40afd727608dfef8002ff7c"
)
doAssert bool a.trySetFromCoordsXandZ(ax, az)
doAssert bool b.trySetFromCoordsXandZ(bx, bz)
doAssert bool c.trySetFromCoordsXandZ(cx, cz)
testAddAssociativity(a, b, c)

View File

@ -11,8 +11,8 @@ import
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/io/io_bigints,
../constantine/[arithmetic, primitives],
../constantine/io/[io_bigints, io_fields, io_ec],
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
@ -29,46 +29,55 @@ run_EC_mul_sanity_tests(
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BN254_Snarks
)
suite "Order checks on BN254_Snarks":
test "EC mul [Order]P == Inf":
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "test_ec_weierstrass_projective_g1_mul_sanity_extra_curve_order_mul_sanity xoshiro512** seed: ", seed
proc test(EC: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let exponent = EC.F.C.getCurveOrder()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = false)
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = true)
# TODO: BLS12 is using a subgroup of order "r" such as r*h = CurveOrder
# with h the curve cofactor
# instead of the full group
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = false)
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = true)
test "Not a point on the curve / not a square - #67":
var ax, ay: Fp[BN254_Snarks]
ax.fromHex"0x2a74c9ca553cd5f3437b41e77ca0c8cc77567a7eca5e7debc55b146b0bee324b"
ay.curve_eq_rhs(ax)
check:
bool not ay.isSquare()
bool not ay.sqrt_if_square()
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_381
)
test "EC mul [Order]P == Inf":
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "test_ec_weierstrass_projective_g1_mul_sanity_extra_curve_order_mul_sanity xoshiro512** seed: ", seed
proc test(EC: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let exponent = EC.F.C.getCurveOrder()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = false)
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = true)
# TODO: BLS12 is using a subgroup of order "r" such as r*h = CurveOrder
# with h the curve cofactor
# instead of the full group
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = false)
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = true)

View File

@ -12,7 +12,7 @@ import
# Internal
../constantine/arithmetic,
../constantine/io/[io_bigints, io_fields],
../constantine/config/[curves, common],
../constantine/config/[curves, common, type_bigint],
# Test utilities
../helpers/prng_unsafe
@ -159,3 +159,100 @@ suite "Random Modular Squaring is consistent with Modular Multiplication" & " ["
randomHighHammingWeight(BLS12_381)
for _ in 0 ..< Iters:
random_long01Seq(BLS12_381)
suite "Modular squaring - bugs highlighted by property-based testing":
test "a² == (-a)² on for Fp[2^127 - 1] - #61":
var a{.noInit.}: Fp[Mersenne127]
a.fromHex"0x75bfffefbfffffff7fd9dfd800000000"
var na{.noInit.}: Fp[Mersenne127]
na.neg(a)
a.square()
na.square()
check:
bool(a == na)
var a2{.noInit.}, na2{.noInit.}: Fp[Mersenne127]
a2.fromHex"0x75bfffefbfffffff7fd9dfd800000000"
na2.neg(a2)
a2 *= a2
na2 *= na2
check:
bool(a2 == na2)
bool(a2 == a)
bool(a2 == na)
test "a² == (-a)² on for Fp[2^127 - 1] - #62":
var a{.noInit.}: Fp[Mersenne127]
a.fromHex"0x7ff7ffffffffffff1dfb7fafc0000000"
var na{.noInit.}: Fp[Mersenne127]
na.neg(a)
a.square()
na.square()
check:
bool(a == na)
var a2{.noInit.}, na2{.noInit.}: Fp[Mersenne127]
a2.fromHex"0x7ff7ffffffffffff1dfb7fafc0000000"
na2.neg(a2)
a2 *= a2
na2 *= na2
check:
bool(a2 == na2)
bool(a2 == a)
bool(a2 == na)
test "32-bit fast squaring on BLS12-381 - #42":
# x = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)
# p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
# Fp = GF(p)
# a = Fp(Integer('0x091F02EFA1C9B99C004329E94CD3C6B308164CBE02037333D78B6C10415286F7C51B5CD7F917F77B25667AB083314B1B'))
# a2 = a*a
# print('a²: ' + Integer(a2).hex())
var a{.noInit.}, expected{.noInit.}: Fp[BLS12_381]
a.fromHex"0x091F02EFA1C9B99C004329E94CD3C6B308164CBE02037333D78B6C10415286F7C51B5CD7F917F77B25667AB083314B1B"
expected.fromHex"0x129e84715b197f76766c8604002cfc287fbe3d16774e18c599853ce48d03dc26bf882e159323ee3d25e52e4809ff4ccc"
var a2mul = a
var a2sqr = a
a2mul.prod(a, a)
a2sqr.square(a)
check:
bool(a2mul == expected)
bool(a2sqr == expected)
test "32-bit fast squaring on BLS12-381 - #43":
# x = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)
# p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
# Fp = GF(p)
# a = Fp(Integer('0x0B7C8AFE5D43E9A973AF8649AD8C733B97D06A78CFACD214CBE9946663C3F682362E0605BC8318714305B249B505AFD9'))
# a2 = a*a
# print('a²: ' + Integer(a2).hex())
var a{.noInit.}, expected{.noInit.}: Fp[BLS12_381]
a.fromHex"0x0B7C8AFE5D43E9A973AF8649AD8C733B97D06A78CFACD214CBE9946663C3F682362E0605BC8318714305B249B505AFD9"
expected.fromHex"0x94b12b599042198a4ad5ad05ed4da1a3332fe50518b6eb718d258d7e3c60a48a89f7417a0b413b92537c24c9e94e038"
var a2mul = a
var a2sqr = a
a2mul.prod(a, a)
a2sqr.square(a)
check:
bool(a2mul == expected)
bool(a2sqr == expected)

View File

@ -295,3 +295,61 @@ proc main() =
testRandomInv BN462
main()
proc main_anti_regression =
suite "Bug highlighted by property-based testing" & " [" & $WordBitwidth & "-bit mode]":
# test "#30 - Euler's Criterion should be 1 for square on FKM12_447":
# var a: Fp[FKM12_447]
# # square of "0x406e5e74ee09c84fa0c59f2db3ac814a4937e2f57ecd3c0af4265e04598d643c5b772a6549a2d9b825445c34b8ba100fe8d912e61cfda43d"
# a.fromHex("0x1e6511b2bfabd7d32d8df7492c66df29ade7fdb21bb0d8f6cacfccb05e45a812a27cd087e1bbb2d202ee29f75a021a6a68d990a2a5e73410")
# a.powUnsafeExponent(FKM12_447.getPrimeMinus1div2_BE())
# check: bool a.isOne()
test "#42 - a^(p-3)/4 (inverse square root)":
# x = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)
# p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
# Fp = GF(p)
# a = Fp(Integer('0x184d02ce4f24d5e59b4150a57a31b202fd40a4b41d7518c22b84bee475fbcb7763100448ef6b17a6ea603cf062e5db51'))
# inv = a^((p-3)/4)
# print('a^((p-3)/4): ' + Integer(inv).hex())
var a: Fp[BLS12_381]
a.fromHex"0x184d02ce4f24d5e59b4150a57a31b202fd40a4b41d7518c22b84bee475fbcb7763100448ef6b17a6ea603cf062e5db51"
var pm3div4 = BLS12_381.Mod
discard pm3div4.sub SecretWord(3)
pm3div4.shiftRight(2)
a.powUnsafeExponent(pm3div4)
var expected: Fp[BLS12_381]
expected.fromHex"ec6fc6cd4d8a3afe1114d5288759b40a87b6b2f001c8c41693f13132be04de21ca22ea38bded36f3748e06d7b4c348c"
check: bool(a == expected)
test "#43 - a^(p-3)/4 (inverse square root)":
# x = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)
# p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
# Fp = GF(p)
# a = Fp(Integer('0x0f16d7854229d8804bcadd889f70411d6a482bde840d238033bf868e89558d39d52f9df60b2d745e02584375f16c34a3'))
# inv = a^((p-3)/4)
# print('a^((p-3)/4): ' + Integer(inv).hex())
var a: Fp[BLS12_381]
a.fromHex"0x0f16d7854229d8804bcadd889f70411d6a482bde840d238033bf868e89558d39d52f9df60b2d745e02584375f16c34a3"
var pm3div4 = BLS12_381.Mod
discard pm3div4.sub SecretWord(3)
pm3div4.shiftRight(2)
a.powUnsafeExponent(pm3div4)
var expected: Fp[BLS12_381]
expected.fromHex"16bf380e9b6d01aa6961c4fcee02a00cb827b52d0eb2b541ea8b598d32100d0bd7dc9a600852b49f0379e63ba9c5d35e"
check: bool(a == expected)
main_anti_regression()

View File

@ -82,27 +82,27 @@ proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) =
bool not a.sqrt_if_square()
bool (a == a2) # a shouldn't be modified
template testImpl(a: untyped): untyped {.dirty.} =
var na{.noInit.}: typeof(a)
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)
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])
@ -133,4 +133,35 @@ proc main() =
randomSqrtCheck_p3mod4 BLS12_461
randomSqrtCheck_p3mod4 BN462
suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
test "FKM12_447 - #30":
var a: Fp[FKM12_447]
a.fromHex"0x406e5e74ee09c84fa0c59f2db3ac814a4937e2f57ecd3c0af4265e04598d643c5b772a6549a2d9b825445c34b8ba100fe8d912e61cfda43d"
a.square()
check: bool a.isSquare()
test "Fused modular square root on 32-bit - inconsistent with isSquare - #42":
var a: Fp[BLS12_381]
a.fromHex"0x184d02ce4f24d5e59b4150a57a31b202fd40a4b41d7518c22b84bee475fbcb7763100448ef6b17a6ea603cf062e5db51"
check:
bool(not a.isSquare())
bool(not a.sqrt_if_square())
test "Fused modular square root on 32-bit - inconsistent with isSquare - #43":
var a: Fp[BLS12_381]
a.fromHex"0x0f16d7854229d8804bcadd889f70411d6a482bde840d238033bf868e89558d39d52f9df60b2d745e02584375f16c34a3"
check:
bool(not a.isSquare())
bool(not a.sqrt_if_square())
test "Fp[2^127 - 1] - #61":
var a: Fp[Mersenne127]
a.fromHex"0x75bfffefbfffffff7fd9dfd800000000"
testImpl(a)
test "Fp[2^127 - 1] - #62":
var a: Fp[Mersenne127]
a.fromHex"0x7ff7ffffffffffff1dfb7fafc0000000"
testImpl(a)
main()

View File

@ -14,6 +14,7 @@ import
../constantine/[arithmetic, primitives],
../constantine/towers,
../constantine/config/curves,
../constantine/io/io_towers,
# Test utilities
../helpers/prng_unsafe
@ -53,4 +54,25 @@ proc main() =
randomSqrtCheck_p3mod4 BN254_Snarks
randomSqrtCheck_p3mod4 BLS12_381
suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
test "sqrt_if_square invalid square BLS12_381 - #64":
var a: Fp2[BLS12_381]
a.fromHex(
"0x09f7034e1d37628dec7be400ddd098110c9160e1de63637d73bd93796f311fb50d438ef357a9349d245fbcfcb6fccf01",
"0x033c9b2f17988d8bea494fde020f54fb33cc780bba53e4f6746783ac659d472d9f616516fcf87f0d9a980243d38afeee"
)
check:
bool not a.isSquare()
bool not a.sqrt_if_square()
test "sqrt_if_square invalid square BLS12_381 - #65-3":
var a: Fp2[BLS12_381]
a.fromHex(
"0x061bd0f645de26f928386c9393711ba30cabcee5b493f1c3502b33d1cf4e80ed6a9433fe51ec48ce3b28fa748a5cbf93",
"0x105eddcc7fca28805a016b5a01723c632bad32dd8d5de66457dfe73807e226772e653b3e37c3dea0248f98847efa9a85"
)
check:
bool not a.isSquare()
bool not a.sqrt_if_square()
main()

View File

@ -0,0 +1,66 @@
# 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.
import
std/[unittest, times, math],
../constantine/config/common,
../constantine/primitives,
../helpers/prng_unsafe
suite "Extended precision bugs":
test $uint32 & " sanity check":
let a = ct(0x0000_0001, uint32)
let b = ct(0x0000_0001, uint32)
let c = ct(0x0000_0001, uint32)
var hi, lo: Ct[uint32]
muladd1(hi, lo, a, b, c)
check:
hi.uint32 == 0'u32
lo.uint32 == 0x0000_0002'u32
test $uint32 & " muladd1 - #61-1":
let a = ct(0x8000_0001, uint32)
var t = ct(0xE35C_5451, uint32)
var C: Ct[uint32]
muladd1(C, t, a, a, t)
check:
C.uint32 == 0x4000_0001'u32
t.uint32 == 0xe35c_5452'u32
test $uint32 & " muladd1 - #61-2":
let a = ct(0xFFFF_FFFE, uint32)
var t = ct(0x0000_0004, uint32)
var C: Ct[uint32]
muladd1(C, t, a, a, t)
check:
C.uint32 == 0xffff_fffc'u32
t.uint32 == 0x0000_0008'u32
test $uint32 & " muladd1 - #61-3":
let a = ct(0x1480_0020, uint32)
var t = ct(0x5454_109E, uint32)
var C: Ct[uint32]
muladd1(C, t, a, a, t)
check:
C.uint32 == 0x01a4_4005'u32
t.uint32 == 0x7454_149e'u32
test $uint32 & " muladd1 - #62":
let a = ct(0x7FEF_FFFF, uint32)
var t = ct(0x67A4_B24C, uint32)
var C: Ct[uint32]
muladd1(C, t, a, a, t)
check:
C.uint32 == 0x3ff0_00ff'u32
t.uint32 == 0x67c4_b24d'u32