constantine/sage/derive_hash_to_curve.sage
Mamy Ratsimbazafy 65eedd1cf7
Hash-to-Curve BLS12-381 G1 (#189)
* Skeleton of hash to curve for BLS12-381 G1

* Remove isodegree parameter

* Fix polynomial evaluation of hashToG1

* Optimize hash_to_curve and add bench for hash to G1

* slight optim of jacobian isomap + v7 test vectors
2022-04-11 00:57:16 +02:00

471 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/sage
# vim: syntax=python
# vim: set ts=2 sw=2 et:
# 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.
# ############################################################
#
# Frobenius constants
#
# ############################################################
# Imports
# ---------------------------------------------------------
import os
import inspect, textwrap
# Working directory
# ---------------------------------------------------------
os.chdir(os.path.dirname(__file__))
# Sage imports
# ---------------------------------------------------------
# Accelerate arithmetic by accepting probabilistic proofs
from sage.structure.proof.all import arithmetic
arithmetic(False)
load('curves.sage')
# Utilities
# ---------------------------------------------------------
def fp2_to_hex(a):
v = vector(a)
return '0x' + Integer(v[0]).hex() + ' + β * ' + '0x' + Integer(v[1]).hex()
def field_to_nim(value, field, curve, prefix = "", comment_above = "", comment_right = ""):
result = '# ' + comment_above + '\n' if comment_above else ''
comment_right = ' # ' + comment_right if comment_right else ''
if field == 'Fp2':
v = vector(value)
result += inspect.cleandoc(f"""
{prefix}Fp2[{curve}].fromHex( {comment_right}
"0x{Integer(v[0]).hex()}",
"0x{Integer(v[1]).hex()}"
)""")
elif field == 'Fp':
result += inspect.cleandoc(f"""
{prefix}Fp[{curve}].fromHex( {comment_right}
"0x{Integer(value).hex()}")
""")
else:
raise NotImplementedError()
return result
def dump_poly(name, poly, field, curve):
result = f'const {name}* = [\n'
result += ' # Polynomial k₀ + k₁ x + k₂ x² + k₃ x³ + ... + kₙ xⁿ\n'
result += ' # The polynomial is stored as an array of coefficients ordered from k₀ to kₙ\n'
result += '\n'
poly = list(poly)
lastRow = len(poly) - 1
for rowID, val in enumerate(reversed(poly)):
(coef, power) = val
result += textwrap.indent(
field_to_nim(
coef, field, curve,
comment_above = str(power)
),
' ')
result += ',\n' if rowID != lastRow else '\n'
result += ']'
return result
# Unused
# ---------------------------------------------------------
def find_z_sswu(F, A, B):
"""
https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#ref-SAGE
Arguments:
- F, a field object, e.g., F = GF(2^521 - 1)
- A and B, the coefficients of the curve equation y² = x³ + A * x + B
"""
R.<xx> = F[] # Polynomial ring over F
g = xx^3 + F(A) * xx + F(B) # y² = g(x) = x³ + A * x + B
ctr = F.gen()
while True:
for Z_cand in (F(ctr), F(-ctr)):
if Z_cand.is_square():
# Criterion 1: Z is non-square in F.
continue
if Z_cand == F(-1):
# Criterion 2: Z != -1 in F.
continue
if not (g - Z_cand).is_irreducible():
# Criterion 3: g(x) - Z is irreducible over F.
continue
if g(B / (Z_cand * A)).is_square():
# Criterion 4: g(B / (Z * A)) is square in F.
return Z_cand
ctr += 1
# BLS12-381 G1
# ---------------------------------------------------------
# Hardcoding from spec:
# - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.1
# - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/f7dd3761/poc/sswu_opt_3mod4.sage#L126-L132
def genBLS12381G1_H2C_constants(curve_config):
curve_name = 'BLS12_381'
# ------------------------------------------
p = curve_config[curve_name]['field']['modulus']
Fp = GF(p)
K.<u> = PolynomialRing(Fp)
# ------------------------------------------
# Hash to curve isogenous curve parameters
# y² = x³ + A'*x + B'
print('\n----> Hash-to-Curve map to isogenous BLS12-381 E\'1 <----\n')
buf = inspect.cleandoc(f"""
# Hash-to-Curve map to isogenous BLS12-381 E'1 constants
# -----------------------------------------------------------------
#
# y² = x³ + A'*x + B' with p ≡ 3 (mod 4) the BLS12-381 characteristic (base modulus)
#
# Hardcoding from spec:
# - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.1
# - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/f7dd3761/poc/sswu_opt_3mod4.sage#L126-L132
""")
buf += '\n\n'
# Base constants
Aprime_E1 = Fp('0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d')
Bprime_E1 = Fp('0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0')
Z = Fp(11)
# Extra
minus_A = -Aprime_E1
ZmulA = Z * Aprime_E1
sqrt_minus_Z3 = sqrt(-Z^3)
buf += f'const {curve_name}_h2c_G1_Aprime_E1* = '
buf += field_to_nim(Aprime_E1, 'Fp', curve_name)
buf += '\n'
buf += f'const {curve_name}_h2c_G1_Bprime_E1* = '
buf += field_to_nim(Bprime_E1, 'Fp', curve_name)
buf += '\n'
buf += f'const {curve_name}_h2c_G1_Z* = '
buf += field_to_nim(Z, 'Fp', curve_name)
buf += '\n'
buf += f'const {curve_name}_h2c_G1_minus_A* = '
buf += field_to_nim(minus_A, 'Fp', curve_name)
buf += '\n'
buf += f'const {curve_name}_h2c_G1_ZmulA* = '
buf += field_to_nim(ZmulA, 'Fp', curve_name)
buf += '\n'
buf += f'const {curve_name}_h2c_G1_sqrt_minus_Z3* = '
buf += field_to_nim(sqrt_minus_Z3, 'Fp', curve_name)
buf += '\n'
return buf
def genBLS12381G1_H2C_isogeny_map(curve_config):
curve_name = 'BLS12_381'
# Hash to curve isogenous curve parameters
# y² = x³ + A'*x + B'
print('\n----> Hash-to-Curve 3-isogeny map BLS12-381 E\'1 constants <----\n')
buf = inspect.cleandoc(f"""
# 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)
""")
buf += '\n\n'
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 - Isogenous curve E'1, degree 11
Aprime_E1 = Fp('0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d')
Bprime_E1 = Fp('0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0')
Eprime1 = EllipticCurve(Fp, [Aprime_E1, Bprime_E1])
iso = EllipticCurveIsogeny(E=E1, kernel=None, codomain=Eprime1, degree=11).dual()
if (- iso.rational_maps()[1])(1, 1) > iso.rational_maps()[1](1, 1):
iso.switch_sign()
(xm, ym) = iso.rational_maps()
maps = (xm.numerator(), xm.denominator(), ym.numerator(), ym.denominator())
buf += dump_poly(
'BLS12_381_h2c_G1_11_isogeny_map_xnum',
xm.numerator(), 'Fp', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G1_11_isogeny_map_xden',
xm.denominator(), 'Fp', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G1_11_isogeny_map_ynum',
ym.numerator(), 'Fp', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G1_11_isogeny_map_yden',
ym.denominator(), 'Fp', curve_name)
return buf
# BLS12-381 G2
# ---------------------------------------------------------
# Hardcoding from spec:
# - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2
# - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/f7dd3761/poc/sswu_opt_9mod16.sage#L142-L148
def genBLS12381G2_H2C_constants(curve_config):
curve_name = 'BLS12_381'
# ------------------------------------------
embdeg = curve_config[curve_name]['tower']['embedding_degree']
twdeg = curve_config[curve_name]['tower']['twist_degree']
g2field = f'Fp{embdeg//twdeg}' if (embdeg//twdeg) > 1 else 'Fp'
p = curve_config[curve_name]['field']['modulus']
Fp = GF(p)
K.<u> = PolynomialRing(Fp)
if g2field == 'Fp2':
QNR_Fp = curve_config[curve_name]['tower']['QNR_Fp']
Fp2.<beta> = Fp.extension(u^2 - QNR_Fp)
else:
SNR_Fp = curve_config[curve_name]['tower']['SNR_Fp']
Fp2.<beta> = Fp.extension(u^2 - SNR_Fp)
# ------------------------------------------
# Hash to curve isogenous curve parameters
# y² = x³ + A'*x + B'
print('\n----> Hash-to-Curve map to isogenous BLS12-381 E\'2 <----\n')
buf = inspect.cleandoc(f"""
# Hash-to-Curve map to isogenous BLS12-381 E'2 constants
# -----------------------------------------------------------------
#
# y² = x³ + A'*x + B' with p² = q ≡ 9 (mod 16), p the BLS12-381 characteristic (base modulus)
#
# Hardcoding from spec:
# - https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-8.8.2
# - https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/f7dd3761/poc/sswu_opt_9mod16.sage#L142-L148
""")
buf += '\n\n'
# Base constants
Aprime_E2 = Fp2([0, 240])
Bprime_E2 = Fp2([1012, 1012])
Z = Fp2([-2, -1])
# Extra
minus_A = -Aprime_E2
ZmulA = Z * Aprime_E2
inv_Z3 = (Z^3)^-1 # modular inverse of Z³
(a, b) = vector(inv_Z3)
squared_norm_inv_Z3 = a^2 + b^2 # ||1/Z³||²
# x^((p-3)/4)) ≡ 1/√x (mod p) if p ≡ 3 (mod 4)
inv_norm_inv_Z3 = squared_norm_inv_Z3^((p-3)/4) # 1/||1/Z³||
buf += f'const {curve_name}_h2c_G2_Aprime_E2* = '
buf += field_to_nim(Aprime_E2, 'Fp2', curve_name, comment_right = "240𝑖")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_Bprime_E2* = '
buf += field_to_nim(Bprime_E2, 'Fp2', curve_name, comment_right = "1012 * (1 + 𝑖)")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_Z* = '
buf += field_to_nim(Z, 'Fp2', curve_name, comment_right = "-(2 + 𝑖)")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_minus_A* = '
buf += field_to_nim(minus_A, 'Fp2', curve_name, comment_right = "-240𝑖")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_ZmulA* = '
buf += field_to_nim(ZmulA, 'Fp2', curve_name, comment_right = "Z*A = 240-480𝑖")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_inv_Z3* = '
buf += field_to_nim(inv_Z3, 'Fp2', curve_name, comment_right = "1/Z³")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_squared_norm_inv_Z3* = '
buf += field_to_nim(squared_norm_inv_Z3, 'Fp', curve_name, comment_right = "||1/Z³||²")
buf += '\n'
buf += f'const {curve_name}_h2c_G2_inv_norm_inv_Z3* = '
buf += field_to_nim(inv_norm_inv_Z3, 'Fp', curve_name, comment_right = "1/||1/Z³||")
buf += '\n'
return buf
def genBLS12381G2_H2C_isogeny_map(curve_config):
curve_name = 'BLS12_381'
# ------------------------------------------
p = curve_config[curve_name]['field']['modulus']
# This extension field construction
# does not work with isogenies :/
#
# embdeg = curve_config[curve_name]['tower']['embedding_degree']
# twdeg = curve_config[curve_name]['tower']['twist_degree']
# g2field = f'Fp{embdeg//twdeg}' if (embdeg//twdeg) > 1 else 'Fp'
#
# Fp = GF(p)
# K.<u> = PolynomialRing(Fp)
# if g2field == 'Fp2':
# QNR_Fp = curve_config[curve_name]['tower']['QNR_Fp']
# Fp2.<beta> = Fp.extension(u^2 - QNR_Fp)
# else:
# SNR_Fp = curve_config[curve_name]['tower']['SNR_Fp']
# Fp2.<beta> = Fp.extension(u^2 - SNR_Fp)
# ------------------------------------------
QNR_Fp = curve_config[curve_name]['tower']['QNR_Fp']
Fp2.<beta> = GF(p^2, modulus=(x^2-QNR_Fp))
# Hash to curve isogenous curve parameters
# y² = x³ + A'*x + B'
print('\n----> Hash-to-Curve 3-isogeny map BLS12-381 E\'2 constants <----\n')
buf = inspect.cleandoc(f"""
# Hash-to-Curve 3-isogeny map BLS12-381 E'2 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)
""")
buf += '\n\n'
# Base constants - E2
A = curve_config[curve_name]['curve']['a']
B = curve_config[curve_name]['curve']['b']
twist = curve_config[curve_name]['tower']['twist']
SNR_Fp2 = curve_config[curve_name]['tower']['SNR_Fp2']
if twist == 'M_twist':
Btwist = B * Fp2(SNR_Fp2)
else:
Btwist = B / Fp2(SNR_Fp2)
E2 = EllipticCurve(Fp2, [A, B * Fp2(SNR_Fp2)])
# Base constants - Isogenous curve E'2, degree 3
Aprime_E2 = Fp2([0, 240])
Bprime_E2 = Fp2([1012, 1012])
Eprime2 = EllipticCurve(Fp2, [Aprime_E2, Bprime_E2])
iso_kernel = [6 * (1 - beta), 1]
iso = EllipticCurveIsogeny(E=Eprime2, kernel=iso_kernel, codomain=E2, degree=3)
if (- iso.rational_maps()[1])(1, 1) > iso.rational_maps()[1](1, 1):
iso.switch_sign()
(xm, ym) = iso.rational_maps()
maps = (xm.numerator(), xm.denominator(), ym.numerator(), ym.denominator())
buf += dump_poly(
'BLS12_381_h2c_G2_3_isogeny_map_xnum',
xm.numerator(), 'Fp2', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G2_3_isogeny_map_xden',
xm.denominator(), 'Fp2', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G2_3_isogeny_map_ynum',
ym.numerator(), 'Fp2', curve_name)
buf += '\n'
buf += dump_poly(
'BLS12_381_h2c_G2_3_isogeny_map_yden',
ym.denominator(), 'Fp2', curve_name)
return buf
# CLI
# ---------------------------------------------------------
if __name__ == "__main__":
# Usage
# BLS12-381
# sage sage/derive_hash_to_curve.sage BLS12_381 G2
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("curve",nargs="+")
args = parser.parse_args()
curve = args.curve[0]
group = args.curve[1]
if curve == 'BLS12_381' and group == 'G1':
h2c = genBLS12381G1_H2C_constants(Curves)
h2c += '\n\n'
h2c += genBLS12381G1_H2C_isogeny_map(Curves)
with open(f'{curve.lower()}_hash_to_curve_g1.nim', 'w') as f:
f.write(copyright())
f.write('\n\n')
f.write(inspect.cleandoc("""
import
../config/curves,
../io/io_fields
"""))
f.write('\n\n')
f.write(h2c)
print(f'Successfully created {curve.lower()}_hash_to_curve_g1.nim')
elif curve == 'BLS12_381' and group == 'G2':
h2c = genBLS12381G2_H2C_constants(Curves)
h2c += '\n\n'
h2c += genBLS12381G2_H2C_isogeny_map(Curves)
with open(f'{curve.lower()}_hash_to_curve_g2.nim', 'w') as f:
f.write(copyright())
f.write('\n\n')
f.write(inspect.cleandoc("""
import
../config/curves,
../io/[io_fields, io_extfields]
"""))
f.write('\n\n')
f.write(h2c)
print(f'Successfully created {curve.lower()}_hash_to_curve_g2.nim')
else:
raise ValueError(
curve + group +
' is not configured '
)