From d69c7bf8e93e1cf9ab1e38a5450c78cf987c7db2 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 3 Jul 2023 06:57:22 +0200 Subject: [PATCH] Fuzz Fix - Hash-To-Curve - Isogeny EC add non-fully-reduced input (#250) * H2C: fix fuzz failure 2, non-fully reduced in isogeny EC addition * faster hashToG2 by using sparsity --- constantine.nimble | 2 +- constantine/hash_to_curve/hash_to_curve.nim | 27 +++++---- .../elliptic/ec_shortweierstrass_jacobian.nim | 55 +++++++++---------- constantine/math/extension_fields/towers.nim | 12 ++-- tests/t_hash_to_curve_random.nim | 35 +++++++++++- 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/constantine.nimble b/constantine.nimble index c5766b0..b5fe831 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -483,7 +483,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # Hashing to elliptic curves # ---------------------------------------------------------- ("tests/t_hash_to_field.nim", false), - # ("tests/t_hash_to_curve_random.nim", false), + ("tests/t_hash_to_curve_random.nim", false), ("tests/t_hash_to_curve.nim", false), # Protocols diff --git a/constantine/hash_to_curve/hash_to_curve.nim b/constantine/hash_to_curve/hash_to_curve.nim index 60998a2..a5b32c9 100644 --- a/constantine/hash_to_curve/hash_to_curve.nim +++ b/constantine/hash_to_curve/hash_to_curve.nim @@ -133,12 +133,12 @@ func mapToCurve_svdw_fusedAdd[F; G: static Subgroup]( ## finite or extension field F ## to an elliptic curve E ## and add them - var P0{.noInit.}, P1{.noInit.}: ECP_ShortW_Aff[F, G] - P0.mapToCurve_svdw(u0) - P1.mapToCurve_svdw(u1) + var Q0{.noInit.}, Q1{.noInit.}: ECP_ShortW_Aff[F, G] + Q0.mapToCurve_svdw(u0) + Q1.mapToCurve_svdw(u1) - r.fromAffine(P0) - r += P1 + r.fromAffine(Q0) + r += Q1 func mapToCurve_sswu_fusedAdd[F; G: static Subgroup]( r: var ECP_ShortW_Jac[F, G], @@ -164,25 +164,24 @@ func mapToCurve_sswu_fusedAdd[F; G: static Subgroup]( # https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-6.6.3 # Simplified Shallue-van de Woestijne-Ulas method for AB == 0 - var P0{.noInit.}, P1{.noInit.}: ECP_ShortW_Jac[F, G] + var Q0{.noInit.}, Q1{.noInit.}: ECP_ShortW_Jac[F, G] # 1. Map to E' isogenous to E when F is Fp and F.C.has_P_3mod4_primeModulus(): # 1. Map to E'1 isogenous to E1 - P0.mapToIsoCurve_sswuG1_opt3mod4(u0) - P1.mapToIsoCurve_sswuG1_opt3mod4(u1) - P0.sum(P0, P1, h2CConst(F.C, sswu, G1, Aprime_E1)) + Q0.mapToIsoCurve_sswuG1_opt3mod4(u0) + Q1.mapToIsoCurve_sswuG1_opt3mod4(u1) + Q0.sum(Q0, Q1, h2CConst(F.C, sswu, G1, Aprime_E1)) elif F is Fp2 and F.C.has_Psquare_9mod16_primePower(): - # p ≡ 3 (mod 4) => p² ≡ 9 (mod 16) # 1. Map to E'2 isogenous to E2 - P0.mapToIsoCurve_sswuG2_opt9mod16(u0) - P1.mapToIsoCurve_sswuG2_opt9mod16(u1) - P0.sum(P0, P1, h2CConst(F.C, sswu, G2, Aprime_E2)) + Q0.mapToIsoCurve_sswuG2_opt9mod16(u0) + Q1.mapToIsoCurve_sswuG2_opt9mod16(u1) + Q0.sum(Q0, Q1, h2CConst(F.C, sswu, G2, Aprime_E2)) else: {.error: "Not implemented".} # 2. Map from E'2 to E2 - r.h2c_isogeny_map(P0) + r.h2c_isogeny_map(Q0) else: {.error: "Not implemented".} diff --git a/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim b/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim index ebf6fc6..e74e7eb 100644 --- a/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim +++ b/constantine/math/elliptic/ec_shortweierstrass_jacobian.nim @@ -43,7 +43,7 @@ func isInf*(P: ECP_ShortW_Jac): SecretBool {.inline.} = ## ## Note: the jacobian coordinates equation is ## Y² = X³ + aXZ⁴ + bZ⁶ - ## + ## ## When Z = 0 in the equation, it reduces to ## Y² = X³ ## (yZ³)² = (xZ²)³ which is true for any x, y coordinates @@ -209,6 +209,15 @@ template sumImpl[F; G: static Subgroup]( # | Y₃ = R*(V-X₃)-S₁*HHH | Y₃ = M*(S-X₃)-YY*YY | | | # | Z₃ = Z₁*Z₂*H | Z₃ = Y₁*Z₁ | | | + # "when" static evaluation doesn't shortcut booleans :/ + # which causes issues when CoefA isn't an int but Fp or Fp2 + when CoefA is int: + const CoefA_eq_zero = CoefA == 0 + const CoefA_eq_minus3 {.used.} = CoefA == -3 + else: + const CoefA_eq_zero = false + const CoefA_eq_minus3 = false + var Z1Z1 {.noInit.}, U1 {.noInit.}, S1 {.noInit.}, H {.noInit.}, R {.noinit.}: F block: # Addition-only, check for exceptional cases @@ -218,7 +227,7 @@ template sumImpl[F; G: static Subgroup]( S1 *= P.y # S₁ = Y₁*Z₂³ U1.prod(P.x, Z2Z2) # U₁ = X₁*Z₂² - Z1Z1.square(P.z, skipFinalSub = true) + Z1Z1.square(P.z, skipFinalSub = not CoefA_eq_minus3) S2.prod(P.z, Z1Z1, skipFinalSub = true) S2 *= Q.y # S₂ = Y₂*Z₁³ U2.prod(Q.x, Z1Z1) # U₂ = X₂*Z₁² @@ -248,15 +257,6 @@ template sumImpl[F; G: static Subgroup]( V_or_S *= HH_or_YY # V = U₁*HH (add) or S = X₁*YY (dbl) block: # Compute M for doubling - # "when" static evaluation doesn't shortcut booleans :/ - # which causes issues when CoefA isn't an int but Fp or Fp2 - when CoefA is int: - const CoefA_eq_zero = CoefA == 0 - const CoefA_eq_minus3 {.used.} = CoefA == -3 - else: - const CoefA_eq_zero = false - const CoefA_eq_minus3 = false - when CoefA_eq_zero: var a {.noInit.} = H var b {.noInit.} = HH_or_YY @@ -289,14 +289,13 @@ template sumImpl[F; G: static Subgroup]( var b{.noInit.} = HH_or_YY a.ccopy(P.x, isDbl) b.ccopy(P.x, isDbl) - HHH_or_Mpre.prod(a, b, true) # HHH or X₁² + HHH_or_Mpre.prod(a, b) # HHH or X₁² # Assuming doubling path a.square(HHH_or_Mpre, skipFinalSub = true) a *= HHH_or_Mpre # a = 3X₁² b.square(Z1Z1) - # b.mulCheckSparse(CoefA) # TODO: broken static compile-time type inference - b *= CoefA # b = αZZ, with α the "a" coefficient of the curve + b.mulCheckSparse(CoefA) # b = αZZ, with α the "a" coefficient of the curve a += b a.div2() @@ -430,6 +429,17 @@ func madd*[F; G: static Subgroup]( # | Z₃ = Z₁*Z₂*H | Z₃ = Y₁*Z₁ | | | # # For mixed adddition we just set Z₂ = 1 + + # "when" static evaluation doesn't shortcut booleans :/ + # which causes issues when CoefA isn't an int but Fp or Fp2 + const CoefA = F.C.getCoefA() + when CoefA is int: + const CoefA_eq_zero = CoefA == 0 + const CoefA_eq_minus3 {.used.} = CoefA == -3 + else: + const CoefA_eq_zero = false + const CoefA_eq_minus3 = false + var Z1Z1 {.noInit.}, U1 {.noInit.}, S1 {.noInit.}, H {.noInit.}, R {.noinit.}: F block: # Addition-only, check for exceptional cases @@ -437,7 +447,7 @@ func madd*[F; G: static Subgroup]( U1 = P.x S1 = P.y - Z1Z1.square(P.z, skipFinalSub = true) + Z1Z1.square(P.z, skipFinalSub = not CoefA_eq_minus3) S2.prod(P.z, Z1Z1, skipFinalSub = true) S2 *= Q.y # S₂ = Y₂*Z₁³ U2.prod(Q.x, Z1Z1) # U₂ = X₂*Z₁² @@ -467,16 +477,6 @@ func madd*[F; G: static Subgroup]( V_or_S *= HH_or_YY # V = U₁*HH (add) or S = X₁*YY (dbl) block: # Compute M for doubling - # "when" static evaluation doesn't shortcut booleans :/ - # which causes issues when CoefA isn't an int but Fp or Fp2 - const CoefA = F.C.getCoefA() - when CoefA is int: - const CoefA_eq_zero = CoefA == 0 - const CoefA_eq_minus3 {.used.} = CoefA == -3 - else: - const CoefA_eq_zero = false - const CoefA_eq_minus3 = false - when CoefA_eq_zero: var a {.noInit.} = H var b {.noInit.} = HH_or_YY @@ -509,14 +509,13 @@ func madd*[F; G: static Subgroup]( var b{.noInit.} = HH_or_YY a.ccopy(P.x, isDbl) b.ccopy(P.x, isDbl) - HHH_or_Mpre.prod(a, b, true) # HHH or X₁² + HHH_or_Mpre.prod(a, b) # HHH or X₁² # Assuming doubling path a.square(HHH_or_Mpre, skipFinalSub = true) a *= HHH_or_Mpre # a = 3X₁² b.square(Z1Z1) - # b.mulCheckSparse(CoefA) # TODO: broken static compile-time type inference - b *= CoefA # b = αZZ, with α the "a" coefficient of the curve + b.mulCheckSparse(CoefA) # b = αZZ, with α the "a" coefficient of the curve a += b a.div2() diff --git a/constantine/math/extension_fields/towers.nim b/constantine/math/extension_fields/towers.nim index fdb102b..79ebe74 100644 --- a/constantine/math/extension_fields/towers.nim +++ b/constantine/math/extension_fields/towers.nim @@ -1440,11 +1440,11 @@ func mul_sparse_by_x0*(a: var QuadraticExt, sparseB: QuadraticExt) = a.mul_sparse_by_x0(a, sparseB) template mulCheckSparse*(a: var QuadraticExt, b: QuadraticExt) = - when b.isOne().bool: + when isOne(b).bool: discard - elif b.isMinusOne().bool: + elif isMinusOne(b).bool: a.neg() - elif b.c0.isZero().bool and b.c1.isOne().bool: + elif isZero(c0(b)).bool and isOne(c1(b)).bool: var t {.noInit.}: type(a.c0) when fromComplexExtension(b): t.neg(a.c1) @@ -1454,7 +1454,7 @@ template mulCheckSparse*(a: var QuadraticExt, b: QuadraticExt) = t.prod(a.c1, NonResidue) a.c1 = a.c0 a.c0 = t - elif b.c0.isZero().bool and b.c1.isMinusOne().bool: + elif isZero(c0(b)).bool and isMinusOne(c1(b)).bool: var t {.noInit.}: type(a.c0) when fromComplexExtension(b): t = a.c1 @@ -1464,9 +1464,9 @@ template mulCheckSparse*(a: var QuadraticExt, b: QuadraticExt) = t.prod(a.c1, NonResidue) a.c1.neg(a.c0) a.c0.neg(t) - elif b.c0.isZero().bool: + elif isZero(c0(b)).bool: a.mul_sparse_by_0y(b) - elif b.c1.isZero().bool: + elif isZero(c1(b)).bool: a.mul_sparse_by_x0(b) else: a *= b diff --git a/tests/t_hash_to_curve_random.nim b/tests/t_hash_to_curve_random.nim index 09d0859..c0718e9 100644 --- a/tests/t_hash_to_curve_random.nim +++ b/tests/t_hash_to_curve_random.nim @@ -16,6 +16,7 @@ import ../constantine/hash_to_curve/hash_to_curve, ../constantine/hashes, ../constantine/math/constants/zoo_subgroups, + ../constantine/math/io/io_ec, # Test utilities ../helpers/prng_unsafe @@ -54,4 +55,36 @@ suite "Hash-to-curve produces points on curve and in correct subgroup": testH2C_consistency(ECP_ShortW_Aff[Fp[BN254_Snarks], G1]) test "BN254_Snarks G2": for i in 0 ..< Iters: - testH2C_consistency(ECP_ShortW_Aff[Fp2[BN254_Snarks], G2]) \ No newline at end of file + testH2C_consistency(ECP_ShortW_Aff[Fp2[BN254_Snarks], G2]) + +proc testH2C_guidovranken_fuzz_failure_2() = + # From Guido Vranken differential fuzzing + # Summing elliptic curve on an isogeny was mistakenly not fully reducing HHH_or_Mpre + let msg = [ + uint8 0xa7, 0x1b, 0x0a, 0x38, 0xd4, 0x09, 0x2b, 0x3b, + 0xdc, 0x9e, 0x75, 0x0a, 0x27, 0x0a, 0xd5, 0xdd, + 0x16, 0x6f, 0x32, 0x5c, 0x16, 0xf5, 0x6d, 0x2f, + 0x87, 0xbb, 0x6b, 0xf5, 0xd2] + let dst = [ + uint8 0x5a, 0x59, 0xae, 0x59, 0x04, 0x8a, 0x29, 0x0f, + 0x9a, 0xc1, 0x80, 0x26, 0xba, 0x6d, 0xd8, 0x7f, + 0x54, 0xf0, 0x5a, 0x01, 0x49, 0xad, 0x2b, 0x95, + 0xfe] + let aug = [ + uint8 0x70, 0x20, 0xde, 0x7c, 0x51, 0x88, 0x88, 0x54, + 0xf1, 0xaf, 0xa5, 0x06, 0x78, 0x80, 0xde, 0xf0, + 0x0d, 0xdf] + + var r{.noInit}: ECP_ShortW_Jac[Fp[BLS12_381], G1] + sha256.hashToCurve(128, r, aug, msg, dst) + + let expected = ECP_ShortW_Jac[Fp[BLS12_381], G1].fromHex( + x = "0x48f2bbee30aa236feaa7fb924d8a3de3090ff160f9972a8afda302bd248248527dcc59ce195cd5f5a1488417cfc64cc", + y = "0xe91b0a3cdea4981741791c8e9b4287d2f693c6626d8e4408ecaaa473e6ff2f691f5f23f8b7b46bdf3560e7cca67e5bc" + ) + + doAssert bool(r == expected) + +suite "Hash-to-curve anti-regression": + test "BLS12-381 G1 - Fuzzing Failure 2": + testH2C_guidovranken_fuzz_failure_2() \ No newline at end of file