diff --git a/constantine.nimble b/constantine.nimble index be1147a..5579c87 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -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 diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index 97b59a4..00190d5 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -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`` diff --git a/constantine/arithmetic/limbs_montgomery.nim b/constantine/arithmetic/limbs_montgomery.nim index ce5574a..4eeab79 100644 --- a/constantine/arithmetic/limbs_montgomery.nim +++ b/constantine/arithmetic/limbs_montgomery.nim @@ -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) = diff --git a/tests/t_ec_wstrass_prj_edge_cases.nim b/tests/t_ec_wstrass_prj_edge_cases.nim new file mode 100644 index 0000000..1da57a0 --- /dev/null +++ b/tests/t_ec_wstrass_prj_edge_cases.nim @@ -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) diff --git a/tests/t_ec_wstrass_prj_g1_mul_sanity.nim b/tests/t_ec_wstrass_prj_g1_mul_sanity.nim index 1da4861..df8ff99 100644 --- a/tests/t_ec_wstrass_prj_g1_mul_sanity.nim +++ b/tests/t_ec_wstrass_prj_g1_mul_sanity.nim @@ -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) diff --git a/tests/t_finite_fields_mulsquare.nim b/tests/t_finite_fields_mulsquare.nim index b0f84d3..255ce57 100644 --- a/tests/t_finite_fields_mulsquare.nim +++ b/tests/t_finite_fields_mulsquare.nim @@ -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) diff --git a/tests/t_finite_fields_powinv.nim b/tests/t_finite_fields_powinv.nim index d31e1cf..7af8485 100644 --- a/tests/t_finite_fields_powinv.nim +++ b/tests/t_finite_fields_powinv.nim @@ -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() diff --git a/tests/t_finite_fields_sqrt.nim b/tests/t_finite_fields_sqrt.nim index c427729..3762be4 100644 --- a/tests/t_finite_fields_sqrt.nim +++ b/tests/t_finite_fields_sqrt.nim @@ -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() diff --git a/tests/t_fp2_sqrt.nim b/tests/t_fp2_sqrt.nim index 12b7865..2626531 100644 --- a/tests/t_fp2_sqrt.nim +++ b/tests/t_fp2_sqrt.nim @@ -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() diff --git a/tests/t_primitives_extended_precision.nim b/tests/t_primitives_extended_precision.nim new file mode 100644 index 0000000..6af1483 --- /dev/null +++ b/tests/t_primitives_extended_precision.nim @@ -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