constantine/sage/derive_endomorphisms.sage

305 lines
9.8 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.
# ############################################################
#
# Endomorphism acceleration 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 pretty_print_lattice(Lat):
print('Lattice:')
latHex = [['0x' + x.hex() if x >= 0 else '-0x' + (-x).hex() for x in vec] for vec in Lat]
maxlen = max([len(cell) for row in latHex for cell in row])
for row in latHex:
row = ' '.join(cell.rjust(maxlen + 2) for cell in row)
print(row)
def pretty_print_babai(Basis):
print('Babai:')
for i, v in enumerate(Basis):
if v < 0:
print(f' 𝛼\u0305{i}: -0x{Integer(int(-v)).hex()}')
else:
print(f' 𝛼\u0305{i}: 0x{Integer(int(v)).hex()}')
def derive_lattice(r, lambdaR, m):
lat = Matrix(matrix.identity(m))
lat[0, 0] = r
for i in range(1, m):
lat[i, 0] = -lambdaR^i
return lat.LLL()
def derive_babai(r, lattice, m):
basis = m * [0]
basis[0] = r
ahat = vector(basis) * lattice.inverse()
v = int(r).bit_length()
v = int(((v + 64 - 1) // 64) * 64)
return [(a << v) // r for a in ahat]
# TODO: maximum infinity norm
# G1 Endomorphism
# ---------------------------------------------------------
def check_cubic_root_endo(G1, Fp, r, cofactor, lambdaR, phiP):
## Check the Endomorphism for p mod 3 == 1
## Endomorphism can be field multiplication by one of the non-trivial cube root of unity 𝜑
## Rationale:
## curve equation is y² = x³ + b, and y² = (x𝜑)³ + b <=> y² = x³ + b (with 𝜑³ == 1) so we are still on the curve
## this means that multiplying by 𝜑 the x-coordinate is equivalent to a scalar multiplication by some λᵩ
## with λᵩ² + λᵩ + 1 ≡ 0 (mod r) and 𝜑² + 𝜑 + 1 ≡ 0 (mod p), see below.
## Hence we have a 2 dimensional decomposition of the scalar multiplication
## i.e. For any [s]P, we can find a corresponding [k1]P + [k2][λᵩ]P with [λᵩ]P being a simple field multiplication by 𝜑
## Finding cube roots:
## x³1=0 <=> (x1)(x²+x+1) = 0, if x != 1, x solves (x²+x+1) = 0 <=> x = (-1±√3)/2
assert phiP^3 == Fp(1)
assert lambdaR^3 % r == 1
Prand = G1.random_point()
P = Prand * cofactor
assert P != G1([0, 1, 0])
(Px, Py, Pz) = P
Qendo = G1([Px*phiP, Py, Pz])
Qlambda = lambdaR * P
assert P != Qendo
assert P != Qlambda
assert Qendo == Qlambda
print('Endomorphism OK')
def genCubicRootEndo(curve_name, curve_config):
p = curve_config[curve_name]['field']['modulus']
r = curve_config[curve_name]['field']['order']
b = curve_config[curve_name]['curve']['b']
print('Constructing G1')
Fp = GF(p)
G1 = EllipticCurve(Fp, [0, b])
print('Computing cofactor')
cofactor = G1.order() // r
# slow for large inputs - https://pari.math.u-bordeaux.fr/archives/pari-dev-0412/msg00020.html
if curve_name != 'BW6_761':
print('Finding cube roots')
(phi1, phi2) = (Fp(root) for root in Fp(1).nth_root(3, all=True) if root != 1)
(lambda1, lambda2) = (GF(r)(root) for root in GF(r)(1).nth_root(3, all=True) if root != 1)
else:
print('Skip finding cube roots for BW6_761, too slow, use values from paper https://eprint.iacr.org/2020/351')
phi1 = Integer('0x531dc16c6ecd27aa846c61024e4cca6c1f31e53bd9603c2d17be416c5e4426ee4a737f73b6f952ab5e57926fa701848e0a235a0a398300c65759fc45183151f2f082d4dcb5e37cb6290012d96f8819c547ba8a4000002f962140000000002a')
phi2 = Integer('0xcfca638f1500e327035cdf02acb2744d06e68545f7e64c256ab7ae14297a1a823132b971cdefc65870636cb60d217ff87fa59308c07a8fab8579e02ed3cddca5b093ed79b1c57b5fe3f89c11811c1e214983de300000535e7bc00000000060')
lambda1 = Integer('0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001')
lambda2 = Integer('-0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000002')
print('𝜑1 (mod p): 0x' + Integer(phi1).hex())
print('λᵩ1 (mod r): 0x' + Integer(lambda1).hex())
print('𝜑2 (mod p): 0x' + Integer(phi2).hex())
print('λᵩ2 (mod r): 0x' + Integer(lambda2).hex())
# TODO: is there a better way than spray-and-pray?
# TODO: Should we maximize or minimize lambda
# to maximize/minimize the scalar norm?
# TODO: Or is there a way to ensure
# that the Babai basis is mostly positive?
if lambda1 < lambda2:
lambda1, lambda2 = lambda2, lambda1
try:
check_cubic_root_endo(G1, Fp, r, cofactor, int(lambda1), phi1)
except:
print('Failure with:')
print(' 𝜑 (mod p): 0x' + Integer(phi1).hex())
print(' λᵩ (mod r): 0x' + Integer(lambda1).hex())
phi1, phi2 = phi2, phi1
check_cubic_root_endo(G1, Fp, r, cofactor, int(lambda1), phi1)
finally:
print('Success with:')
print(' 𝜑 (mod p): 0x' + Integer(phi1).hex())
print(' λᵩ (mod r): 0x' + Integer(lambda1).hex())
print('Deriving Lattice')
lattice = derive_lattice(r, lambda1, 2)
pretty_print_lattice(lattice)
print('Deriving Babai basis')
babai = derive_babai(r, lattice, 2)
pretty_print_babai(babai)
return phi1, lattice, babai
# G2 Endomorphism
# ---------------------------------------------------------
def genPsiEndo(curve_name, curve_config):
t = curve_config[curve_name]['field']['trace']
r = curve_config[curve_name]['field']['order']
k = curve_config[curve_name]['tower']['embedding_degree']
# Decomposition factor depends on the embedding degree
m = CyclotomicField(k).degree()
# λψ is the trace of Frobenius - 1
lambda_psi = t - 1
print('Deriving Lattice')
lattice = derive_lattice(r, lambda_psi, m)
pretty_print_lattice(lattice)
print('Deriving Babai basis')
babai = derive_babai(r, lattice, m)
pretty_print_babai(babai)
return lattice, babai
# Dump
# ---------------------------------------------------------
def dumpLattice(lattice):
result = ' # (BigInt, isNeg)\n'
lastRow = lattice.nrows() - 1
lastCol = lattice.ncols() - 1
for rowID, row in enumerate(lattice):
for colID, val in enumerate(row):
result += ' '
result += '(' if colID == 0 else ' '
result += f'(BigInt[{max(1, int(abs(val)).bit_length())}].fromHex"0x{Integer(int(abs(val))).hex()}", '
result += ('false' if val >= 0 else 'true') + ')'
result += ')' if colID == lastCol else ''
result += ',\n' if (rowID != lastRow or colID != lastCol) else '\n'
return result
def dumpBabai(vec):
result = ' # (BigInt, isNeg)\n'
lastRow = len(vec) - 1
for rowID, val in enumerate(vec):
result += ' '
result += f'(BigInt[{max(1, int(abs(val)).bit_length())}].fromHex"0x{Integer(int(abs(val))).hex()}", '
result += ('false' if val >= 0 else 'true') + ')'
result += ',\n' if rowID != lastRow else '\n'
return result
def dumpConst(name, inner):
result = f'const {name}* = (\n'
result += inner
result += ')\n'
return result
# CLI
# ---------------------------------------------------------
if __name__ == "__main__":
# Usage
# BLS12-381
# sage sage/derive_pairing.sage BLS12_381
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("curve",nargs="+")
args = parser.parse_args()
curve = args.curve[0]
if curve not in Curves:
raise ValueError(
curve +
' is not one of the available curves: ' +
str(Curves.keys())
)
else:
print('\nPrecomputing G1 - 𝜑 (phi) cubic root endomorphism')
print('----------------------------------------------------\n')
cubeRootModP, g1lat, g1babai = genCubicRootEndo(curve, Curves)
print('\n\nPrecomputing G2 - ψ (Psi) - untwist-Frobenius-twist endomorphism')
print('----------------------------------------------------\n')
g2lat, g2babai = genPsiEndo(curve, Curves)
with open(f'{curve.lower()}_glv.nim', 'w') as f:
f.write(copyright())
f.write('\n\n')
f.write(inspect.cleandoc(f"""
import
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# {curve} G1
# ------------------------------------------------------------
"""))
f.write('\n\n')
f.write(inspect.cleandoc(f"""
const {curve}_cubicRootOfUnity_mod_p* =
Fp[{curve}].fromHex"0x{Integer(cubeRootModP).hex()}"
"""))
f.write('\n\n')
f.write(dumpConst(
f'{curve}_Lattice_G1',
dumpLattice(g1lat)
))
f.write('\n')
f.write(dumpConst(
f'{curve}_Babai_G1',
dumpBabai(g1babai)
))
f.write('\n\n')
f.write(inspect.cleandoc(f"""
# {curve} G2
# ------------------------------------------------------------
"""))
f.write('\n\n')
f.write(dumpConst(
f'{curve}_Lattice_G2',
dumpLattice(g2lat)
))
f.write('\n')
f.write(dumpConst(
f'{curve}_Babai_G2',
dumpBabai(g2babai)
))