Try to use hash-to-curve for BN254_Snarks but no low-degree isogeny [skip ci]

This commit is contained in:
Mamy Ratsimbazafy 2022-04-12 23:40:07 +02:00
parent 65eedd1cf7
commit 062ae56867
No known key found for this signature in database
GPG Key ID: 6227262F49BE273A
5 changed files with 131 additions and 24 deletions

View File

@ -132,9 +132,9 @@ proc sqrtBench*(T: typedesc, iters: int) =
let x = rng.random_unsafe(T) let x = rng.random_unsafe(T)
const algoType = block: const algoType = block:
when T.C.hasP3mod4_primeModulus(): when T.C.has_P_3mod4_primeModulus():
"p ≡ 3 (mod 4)" "p ≡ 3 (mod 4)"
elif T.C.hasP5mod8_primeModulus(): elif T.C.has_P_5mod8_primeModulus():
"p ≡ 5 (mod 8)" "p ≡ 5 (mod 8)"
else: else:
"Tonelli-Shanks" "Tonelli-Shanks"

View File

@ -84,13 +84,13 @@ func mapToCurve[F; G: static Subgroup](
# Simplified Shallue-van de Woestijne-Ulas method for AB == 0 # Simplified Shallue-van de Woestijne-Ulas method for AB == 0
# 1. Map to E' isogenous to E # 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( mapToIsoCurve_sswuG1_opt3mod4(
xn, xd, xn, xd,
yn, yn,
u, xd3 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) # p ≡ 3 (mod 4) => p² ≡ 9 (mod 16)
mapToIsoCurve_sswuG2_opt9mod16( mapToIsoCurve_sswuG2_opt9mod16(
xn, xd, xn, xd,
@ -131,12 +131,12 @@ func mapToCurve_fusedAdd[F; G: static Subgroup](
# Simplified Shallue-van de Woestijne-Ulas method for AB == 0 # Simplified Shallue-van de Woestijne-Ulas method for AB == 0
# 1. Map to E' isogenous to E # 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 # 1. Map to E'1 isogenous to E1
P0.mapToIsoCurve_sswuG1_opt3mod4(u0) P0.mapToIsoCurve_sswuG1_opt3mod4(u0)
P1.mapToIsoCurve_sswuG1_opt3mod4(u1) P1.mapToIsoCurve_sswuG1_opt3mod4(u1)
P0.sum(P0, P1, h2CConst(F.C, G1, Aprime_E1)) 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) # p ≡ 3 (mod 4) => p² ≡ 9 (mod 16)
# 1. Map to E'2 isogenous to E2 # 1. Map to E'2 isogenous to E2
P0.mapToIsoCurve_sswuG2_opt9mod16(u0) P0.mapToIsoCurve_sswuG2_opt9mod16(u0)

View File

@ -44,7 +44,7 @@ func invsqrt_p3mod4(r: var Fp, a: Fp) =
# a^((p-1)/2)) * a^-1 ≡ 1/a (mod p) # a^((p-1)/2)) * a^-1 ≡ 1/a (mod p)
# a^((p-3)/2)) ≡ 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) # 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(): when FP.C.hasSqrtAddchain():
r.invsqrt_addchain(a) r.invsqrt_addchain(a)
else: else:
@ -104,7 +104,7 @@ func invsqrt_p5mod8(r: var Fp, a: Fp) =
# #
# Hence we set β = (2a)^((p-1)/4) # Hence we set β = (2a)^((p-1)/4)
# and α = (β/2a)⁽¹⸍²⁾= (2a)^(((p-1)/4 - 1)/2) = (2a)^((p-5)/8) # 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 var alpha{.noInit.}, beta{.noInit.}: Fp
# α = (2a)^((p-5)/8) # α = (2a)^((p-5)/8)
@ -234,9 +234,9 @@ func invsqrt*[C](r: var Fp[C], a: Fp[C]) =
## i.e. both x² == (-x)² ## i.e. both x² == (-x)²
## This procedure returns a deterministic result ## This procedure returns a deterministic result
## This procedure is constant-time ## This procedure is constant-time
when C.hasP3mod4_primeModulus(): when C.has_P_3mod4_primeModulus():
r.invsqrt_p3mod4(a) r.invsqrt_p3mod4(a)
elif C.hasP5mod8_primeModulus(): elif C.has_P_5mod8_primeModulus():
r.invsqrt_p5mod8(a) r.invsqrt_p5mod8(a)
else: else:
r.invsqrt_tonelli_shanks(a) r.invsqrt_tonelli_shanks(a)
@ -334,7 +334,7 @@ func isSquare*(a: Fp): SecretBool =
) )
else: else:
# We reuse the optimized addition chains instead of exponentiation by (p-1)/2 # 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 var sqrt{.noInit.}, invsqrt{.noInit.}: Fp
return sqrt_invsqrt_if_square(sqrt, invsqrt, a) return sqrt_invsqrt_if_square(sqrt, invsqrt, a)
else: else:

View File

@ -39,14 +39,18 @@ template matchingLimbs2x*(C: Curve): untyped =
const N2 = wordsRequired(getCurveBitwidth(C)) * 2 # TODO upstream, not precomputing N2 breaks semcheck const N2 = wordsRequired(getCurveBitwidth(C)) * 2 # TODO upstream, not precomputing N2 breaks semcheck
array[N2, SecretWord] # TODO upstream, using Limbs[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) ## Returns true iff p ≡ 3 (mod 4)
(BaseType(C.Mod.limbs[0]) and 3) == 3 (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) ## Returns true iff p ≡ 5 (mod 8)
(BaseType(C.Mod.limbs[0]) and 7) == 5 (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) ## Returns true iff p ≡ 9 (mod 16)
(BaseType(C.Mod.limbs[0]) and 15) == 9 (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

View File

@ -21,6 +21,7 @@
import os import os
import inspect, textwrap import inspect, textwrap
import sage.schemes.elliptic_curves.isogeny_small_degree as isd
# Working directory # Working directory
# --------------------------------------------------------- # ---------------------------------------------------------
@ -86,9 +87,24 @@ def dump_poly(name, poly, field, curve):
result += ']' result += ']'
return 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 = + 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): def find_z_sswu(F, A, B):
""" """
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#ref-SAGE 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 return Z_cand
ctr += 1 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.<u> = PolynomialRing(Fp)
if G2_field == 'Fp2':
Fp2.<beta> = 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 # BLS12-381 G1
# --------------------------------------------------------- # ---------------------------------------------------------
# Hardcoding from spec: # 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 # Hash-to-Curve 11-isogeny map BLS12-381 E'1 constants
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# #
# The polynomials map a point (x', y') on the isogenous curve E'2 # The polynomials map a point (x', y') on the isogenous curve E'1
# to (x, y) on E2, represented as (xnum/xden, y' * ynum/yden) # to (x, y) on E1, represented as (xnum/xden, y' * ynum/yden)
""") """)
buf += '\n\n' buf += '\n\n'
@ -374,7 +470,7 @@ def genBLS12381G2_H2C_isogeny_map(curve_config):
else: else:
Btwist = B / Fp2(SNR_Fp2) 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 # Base constants - Isogenous curve E'2, degree 3
Aprime_E2 = Fp2([0, 240]) Aprime_E2 = Fp2([0, 240])
@ -413,7 +509,11 @@ def genBLS12381G2_H2C_isogeny_map(curve_config):
if __name__ == "__main__": if __name__ == "__main__":
# Usage # Usage
# BLS12-381 # 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 from argparse import ArgumentParser
@ -422,9 +522,12 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
curve = args.curve[0] 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 = genBLS12381G1_H2C_constants(Curves)
h2c += '\n\n' h2c += '\n\n'
h2c += genBLS12381G1_H2C_isogeny_map(Curves) h2c += genBLS12381G1_H2C_isogeny_map(Curves)
@ -444,7 +547,7 @@ if __name__ == "__main__":
print(f'Successfully created {curve.lower()}_hash_to_curve_g1.nim') 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 = genBLS12381G2_H2C_constants(Curves)
h2c += '\n\n' h2c += '\n\n'
h2c += genBLS12381G2_H2C_isogeny_map(Curves) h2c += genBLS12381G2_H2C_isogeny_map(Curves)
@ -465,6 +568,6 @@ if __name__ == "__main__":
print(f'Successfully created {curve.lower()}_hash_to_curve_g2.nim') print(f'Successfully created {curve.lower()}_hash_to_curve_g2.nim')
else: else:
raise ValueError( raise ValueError(
curve + group + curve + group_or_iso +
' is not configured ' ' is not configured '
) )