diff --git a/benchmarks/bench_fields_template.nim b/benchmarks/bench_fields_template.nim index e1527a4..0f0395f 100644 --- a/benchmarks/bench_fields_template.nim +++ b/benchmarks/bench_fields_template.nim @@ -132,9 +132,9 @@ proc sqrtBench*(T: typedesc, iters: int) = let x = rng.random_unsafe(T) const algoType = block: - when T.C.hasP3mod4_primeModulus(): + when T.C.has_P_3mod4_primeModulus(): "p ≡ 3 (mod 4)" - elif T.C.hasP5mod8_primeModulus(): + elif T.C.has_P_5mod8_primeModulus(): "p ≡ 5 (mod 8)" else: "Tonelli-Shanks" diff --git a/constantine/hash_to_curve/hash_to_curve.nim b/constantine/hash_to_curve/hash_to_curve.nim index ddd9d24..1f4f3ca 100644 --- a/constantine/hash_to_curve/hash_to_curve.nim +++ b/constantine/hash_to_curve/hash_to_curve.nim @@ -84,13 +84,13 @@ func mapToCurve[F; G: static Subgroup]( # Simplified Shallue-van de Woestijne-Ulas method for AB == 0 # 1. Map to E' isogenous to E - when F is Fp and F.C.hasP3mod4_primeModulus(): + when F is Fp and F.C.has_P_3mod4_primeModulus(): mapToIsoCurve_sswuG1_opt3mod4( xn, xd, yn, u, xd3 ) - elif F is Fp2 and F.C.hasP3mod4_primeModulus(): + elif F is Fp2 and F.C.has_Psquare_9mod16_primePower(): # p ≡ 3 (mod 4) => p² ≡ 9 (mod 16) mapToIsoCurve_sswuG2_opt9mod16( xn, xd, @@ -131,12 +131,12 @@ func mapToCurve_fusedAdd[F; G: static Subgroup]( # Simplified Shallue-van de Woestijne-Ulas method for AB == 0 # 1. Map to E' isogenous to E - when F is Fp and F.C.hasP3mod4_primeModulus(): + 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, G1, Aprime_E1)) - elif F is Fp2 and F.C.hasP3mod4_primeModulus(): + 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) diff --git a/constantine/math/arithmetic/finite_fields_square_root.nim b/constantine/math/arithmetic/finite_fields_square_root.nim index 7b11e29..3ebc076 100644 --- a/constantine/math/arithmetic/finite_fields_square_root.nim +++ b/constantine/math/arithmetic/finite_fields_square_root.nim @@ -44,7 +44,7 @@ func invsqrt_p3mod4(r: var Fp, a: Fp) = # a^((p-1)/2)) * a^-1 ≡ 1/a (mod p) # a^((p-3)/2)) ≡ 1/a (mod p) # a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4) - static: doAssert Fp.C.hasP3mod4_primeModulus() + static: doAssert Fp.C.has_P_3mod4_primeModulus() when FP.C.hasSqrtAddchain(): r.invsqrt_addchain(a) else: @@ -104,7 +104,7 @@ func invsqrt_p5mod8(r: var Fp, a: Fp) = # # Hence we set β = (2a)^((p-1)/4) # and α = (β/2a)⁽¹⸍²⁾= (2a)^(((p-1)/4 - 1)/2) = (2a)^((p-5)/8) - static: doAssert Fp.C.hasP5mod8_primeModulus() + static: doAssert Fp.C.has_P_5mod8_primeModulus() var alpha{.noInit.}, beta{.noInit.}: Fp # α = (2a)^((p-5)/8) @@ -234,9 +234,9 @@ func invsqrt*[C](r: var Fp[C], a: Fp[C]) = ## i.e. both x² == (-x)² ## This procedure returns a deterministic result ## This procedure is constant-time - when C.hasP3mod4_primeModulus(): + when C.has_P_3mod4_primeModulus(): r.invsqrt_p3mod4(a) - elif C.hasP5mod8_primeModulus(): + elif C.has_P_5mod8_primeModulus(): r.invsqrt_p5mod8(a) else: r.invsqrt_tonelli_shanks(a) @@ -334,7 +334,7 @@ func isSquare*(a: Fp): SecretBool = ) else: # We reuse the optimized addition chains instead of exponentiation by (p-1)/2 - when Fp.C.hasP3mod4_primeModulus() or Fp.C.hasP5mod8_primeModulus(): + when Fp.C.has_P_3mod4_primeModulus() or Fp.C.has_P_5mod8_primeModulus(): var sqrt{.noInit.}, invsqrt{.noInit.}: Fp return sqrt_invsqrt_if_square(sqrt, invsqrt, a) else: diff --git a/constantine/math/config/curves_prop_field_core.nim b/constantine/math/config/curves_prop_field_core.nim index 768029f..8458be5 100644 --- a/constantine/math/config/curves_prop_field_core.nim +++ b/constantine/math/config/curves_prop_field_core.nim @@ -39,14 +39,18 @@ template matchingLimbs2x*(C: Curve): untyped = const N2 = wordsRequired(getCurveBitwidth(C)) * 2 # TODO upstream, not precomputing N2 breaks semcheck array[N2, SecretWord] # TODO upstream, using Limbs[N2] breaks semcheck -func hasP3mod4_primeModulus*(C: static Curve): static bool = +func has_P_3mod4_primeModulus*(C: static Curve): static bool = ## Returns true iff p ≡ 3 (mod 4) (BaseType(C.Mod.limbs[0]) and 3) == 3 -func hasP5mod8_primeModulus*(C: static Curve): static bool = +func has_P_5mod8_primeModulus*(C: static Curve): static bool = ## Returns true iff p ≡ 5 (mod 8) (BaseType(C.Mod.limbs[0]) and 7) == 5 -func hasP9mod16_primeModulus*(C: static Curve): static bool = +func has_P_9mod16_primeModulus*(C: static Curve): static bool = ## Returns true iff p ≡ 9 (mod 16) - (BaseType(C.Mod.limbs[0]) and 15) == 9 \ No newline at end of file + (BaseType(C.Mod.limbs[0]) and 15) == 9 + +func has_Psquare_9mod16_primePower*(C: static Curve): static bool = + ## Returns true iff p² ≡ 9 (mod 16) + ((BaseType(C.Mod.limbs[0]) * BaseType(C.Mod.limbs[0])) and 15) == 9 \ No newline at end of file diff --git a/sage/derive_hash_to_curve.sage b/sage/derive_hash_to_curve.sage index d627d54..bbfafa5 100644 --- a/sage/derive_hash_to_curve.sage +++ b/sage/derive_hash_to_curve.sage @@ -21,6 +21,7 @@ import os import inspect, textwrap +import sage.schemes.elliptic_curves.isogeny_small_degree as isd # Working directory # --------------------------------------------------------- @@ -86,9 +87,24 @@ def dump_poly(name, poly, field, curve): result += ']' return result -# Unused +# Isogenies # --------------------------------------------------------- +def find_iso(E): + """ + Find an isogenous curve with j-invariant not in {0, 1728} so that + Simplified Shallue-van de Woestijne method is directly applicable + (i.e the Elliptic Curve coefficient y² = x³ + A*x + B have AB != 0) + """ + for p_test in primes(30): + isos = [i for i in isd.isogenies_prime_degree(E, p_test) + if i.codomain().j_invariant() not in (0, 1728) ] + if len(isos) > 0: + print(f'Found {len(isos)} isogenous curves of degree {p_test}') + return isos[0].dual() + print(f'Found no isogenies') + return None + def find_z_sswu(F, A, B): """ https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#ref-SAGE @@ -115,6 +131,86 @@ def find_z_sswu(F, A, B): return Z_cand ctr += 1 +def search_isogeny(curve_name, curve_config): + p = curve_config[curve_name]['field']['modulus'] + Fp = GF(p) + + # Base constants - E1 + A = curve_config[curve_name]['curve']['a'] + B = curve_config[curve_name]['curve']['b'] + E1 = EllipticCurve(Fp, [A, B]) + + # Base constants - E2 + embedding_degree = curve_config[curve_name]['tower']['embedding_degree'] + twist_degree = curve_config[curve_name]['tower']['twist_degree'] + twist = curve_config[curve_name]['tower']['twist'] + + G2_field_degree = embedding_degree // twist_degree + G2_field = f'Fp{G2_field_degree}' if G2_field_degree > 1 else 'Fp' + + if G2_field_degree == 2: + non_residue_fp = curve_config[curve_name]['tower']['QNR_Fp'] + elif G2_field_degree == 1: + if twist_degree == 6: + # Only for complete serialization + non_residue_fp = curve_config[curve_name]['tower']['SNR_Fp'] + else: + raise NotImplementedError() + else: + raise NotImplementedError() + + Fp = GF(p) + K. = PolynomialRing(Fp) + + if G2_field == 'Fp2': + Fp2. = Fp.extension(u^2 - non_residue_fp) + G2F = Fp2 + if twist_degree == 6: + non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp2'] + else: + raise NotImplementedError() + elif G2_field == 'Fp': + G2F = Fp + if twist_degree == 6: + non_residue_twist = curve_config[curve_name]['tower']['SNR_Fp'] + else: + raise NotImplementedError() + else: + raise NotImplementedError() + + if twist == 'D_Twist': + G2B = B/G2F(non_residue_twist) + E2 = EllipticCurve(G2F, [0, G2B]) + elif twist == 'M_Twist': + G2B = B*G2F(non_residue_twist) + E2 = EllipticCurve(G2F, [0, G2B]) + else: + raise ValueError('E2 must be a D_Twist or M_Twist but found ' + twist) + + + # Isogenies: + iso_G1 = find_iso(E1) + a_G1 = iso_G1.domain().a4() + b_G1 = iso_G1.domain().a6() + + iso_G2 = find_iso(E2) + a_G2 = iso_G2.domain().a4() + b_G2 = iso_G2.domain().a6() + + # Z + Z_G1 = find_z_sswu(Fp, a_G1, b_G1) + Z_G2 = find_z_sswu(Fp2, a_G2, b_G2) + + print(f"{curve_name} G1 - isogeny of degree {iso_G1.degree()} with eq y² = x³ + A'x + B':") + print(f" A': 0x{Integer(a_G1).hex()}") + print(f" B': 0x{Integer(b_G1).hex()}") + print(f" Z: {Z_G1}") + + print(f"{curve_name} G2 - isogeny of degree {iso_G2.degree()} with eq y² = x³ + A'x + B':") + print(f" A': {fp2_to_hex(a_G2)}") + print(f" B': {fp2_to_hex(b_G2)}") + print(f" Z: {fp2_to_hex(Z_G2)}") + # BLS12-381 G1 # --------------------------------------------------------- # Hardcoding from spec: @@ -192,8 +288,8 @@ def genBLS12381G1_H2C_isogeny_map(curve_config): # Hash-to-Curve 11-isogeny map BLS12-381 E'1 constants # ----------------------------------------------------------------- # - # The polynomials map a point (x', y') on the isogenous curve E'2 - # to (x, y) on E2, represented as (xnum/xden, y' * ynum/yden) + # The polynomials map a point (x', y') on the isogenous curve E'1 + # to (x, y) on E1, represented as (xnum/xden, y' * ynum/yden) """) buf += '\n\n' @@ -374,7 +470,7 @@ def genBLS12381G2_H2C_isogeny_map(curve_config): else: Btwist = B / Fp2(SNR_Fp2) - E2 = EllipticCurve(Fp2, [A, B * Fp2(SNR_Fp2)]) + E2 = EllipticCurve(Fp2, [A, Btwist]) # Base constants - Isogenous curve E'2, degree 3 Aprime_E2 = Fp2([0, 240]) @@ -413,7 +509,11 @@ def genBLS12381G2_H2C_isogeny_map(curve_config): if __name__ == "__main__": # Usage # BLS12-381 - # sage sage/derive_hash_to_curve.sage BLS12_381 G2 + # sage sage/derive_hash_to_curve.sage BLS12_381 G2 + # for Hash-to-Curve + # or + # sage sage/derive_hash_to_curve.sage BLS12_381 iso + # to search for a suitable isogeny from argparse import ArgumentParser @@ -422,9 +522,12 @@ if __name__ == "__main__": args = parser.parse_args() curve = args.curve[0] - group = args.curve[1] + group_or_iso = args.curve[1] - if curve == 'BLS12_381' and group == 'G1': + if group_or_iso == 'iso': + search_isogeny(curve, Curves) + + elif curve == 'BLS12_381' and group_or_iso == 'G1': h2c = genBLS12381G1_H2C_constants(Curves) h2c += '\n\n' h2c += genBLS12381G1_H2C_isogeny_map(Curves) @@ -444,7 +547,7 @@ if __name__ == "__main__": print(f'Successfully created {curve.lower()}_hash_to_curve_g1.nim') - elif curve == 'BLS12_381' and group == 'G2': + elif curve == 'BLS12_381' and group_or_iso == 'G2': h2c = genBLS12381G2_H2C_constants(Curves) h2c += '\n\n' h2c += genBLS12381G2_H2C_isogeny_map(Curves) @@ -465,6 +568,6 @@ if __name__ == "__main__": print(f'Successfully created {curve.lower()}_hash_to_curve_g2.nim') else: raise ValueError( - curve + group + + curve + group_or_iso + ' is not configured ' )