-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathderive_endomorphisms.sage
312 lines (252 loc) · 10.1 KB
/
derive_endomorphisms.sage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#!/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 <=> (x−1)(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
print('cofactor: 0x' + Integer(cofactor).hex())
# 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
# 𝔾₂ 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_endomorphisms.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)
hasG2 = 'tower' in Curves[curve]
if hasG2:
print('\n\nPrecomputing 𝔾₂ - ψ (Psi) - untwist-Frobenius-twist endomorphism')
print('----------------------------------------------------\n')
g2lat, g2babai = genPsiEndo(curve, Curves)
with open(f'{curve.lower()}_endomorphisms.nim', 'w') as f:
f.write(copyright())
f.write('\n\n')
f.write(inspect.cleandoc(f"""
import
constantine/named/algebras,
constantine/math/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')
if hasG2:
f.write('\n')
f.write(inspect.cleandoc(f"""
# {curve} 𝔾₂
# ------------------------------------------------------------
"""))
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)
))
f.write('\n')