224 lines
5.6 KiB
Python
224 lines
5.6 KiB
Python
|
# 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.
|
|||
|
|
|||
|
# ############################################################
|
|||
|
#
|
|||
|
# BLS12-381 GLS Endomorphism
|
|||
|
# Lattice Decomposition
|
|||
|
#
|
|||
|
# ############################################################
|
|||
|
|
|||
|
# Parameters
|
|||
|
x = 3 * 2^46 * (7 * 13 * 499) + 1
|
|||
|
p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
|
|||
|
r = x^4 - x^2 + 1
|
|||
|
print('p : ' + p.hex())
|
|||
|
print('r : ' + r.hex())
|
|||
|
|
|||
|
# Cube root of unity (mod r) formula for any BLS12 curves
|
|||
|
lambda1_r = x^2 - 1
|
|||
|
assert lambda1_r^3 % r == 1
|
|||
|
print('λᵩ1 : ' + lambda1_r.hex())
|
|||
|
print('λᵩ1+r: ' + (lambda1_r+r).hex())
|
|||
|
|
|||
|
lambda2_r = x^4
|
|||
|
assert lambda2_r^3 % r == 1
|
|||
|
print('λᵩ2 : ' + lambda2_r.hex())
|
|||
|
|
|||
|
# Finite fields
|
|||
|
F = GF(p)
|
|||
|
|
|||
|
# Curves
|
|||
|
b = 1
|
|||
|
G1 = EllipticCurve(F, [0, b])
|
|||
|
|
|||
|
cofactorG1 = G1.order() // r
|
|||
|
|
|||
|
print('')
|
|||
|
print('cofactor G1: ' + cofactorG1.hex())
|
|||
|
print('')
|
|||
|
|
|||
|
(phi1, phi2) = (root for root in GF(p)(1).nth_root(3, all=True) if root != 1)
|
|||
|
print('𝜑1 :' + Integer(phi1).hex())
|
|||
|
print('𝜑2 :' + Integer(phi2).hex())
|
|||
|
assert phi1^3 % p == 1
|
|||
|
assert phi2^3 % p == 1
|
|||
|
|
|||
|
def clearCofactorG1(P):
|
|||
|
return cofactorG1 * P
|
|||
|
|
|||
|
# Test generator
|
|||
|
set_random_seed(1337)
|
|||
|
|
|||
|
# Check
|
|||
|
def checkEndo():
|
|||
|
Prand = G1.random_point()
|
|||
|
P = clearCofactorG1(Prand)
|
|||
|
assert P != G1([0, 1, 0]) # Infinity
|
|||
|
|
|||
|
(Px, Py, Pz) = P
|
|||
|
Qendo1 = G1([Px*phi1 % p, Py, Pz])
|
|||
|
Qendo2 = G1([Px*phi2 % p, Py, Pz])
|
|||
|
|
|||
|
Q1 = lambda1_r * P
|
|||
|
Q2 = lambda2_r * P
|
|||
|
|
|||
|
assert P != Q1
|
|||
|
assert P != Q2
|
|||
|
|
|||
|
assert (F(Px)*F(phi1))^3 == F(Px)^3
|
|||
|
assert (F(Px)*F(phi2))^3 == F(Px)^3
|
|||
|
|
|||
|
assert Q1 == Qendo1
|
|||
|
assert Q2 == Qendo1
|
|||
|
|
|||
|
print('Endomorphism OK with 𝜑1')
|
|||
|
|
|||
|
checkEndo()
|
|||
|
|
|||
|
# Decomposition generated by LLL-algorithm and Babai rounding
|
|||
|
# to solve the Shortest (Basis) Vector Problem
|
|||
|
# Lattice from Guide to Pairing-Based Cryptography
|
|||
|
Lat = [
|
|||
|
[x^2-1, -1],
|
|||
|
[1, x^2]
|
|||
|
]
|
|||
|
ahat = [x^2, 1]
|
|||
|
n = int(r).bit_length()
|
|||
|
n = int(((n + 64 - 1) // 64) * 64) # round to next multiple of 64
|
|||
|
v = [Integer(a << n) // r for a in ahat]
|
|||
|
|
|||
|
def pretty_print_lattice(Lat):
|
|||
|
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)
|
|||
|
|
|||
|
print('\nLattice')
|
|||
|
pretty_print_lattice(Lat)
|
|||
|
|
|||
|
print('\nbasis:')
|
|||
|
print(' 𝛼\u03050: 0x' + v[0].hex())
|
|||
|
print(' 𝛼\u03051: 0x' + v[1].hex())
|
|||
|
print('')
|
|||
|
|
|||
|
maxInfNorm = abs(x^2 + 1)
|
|||
|
print('\nmax infinity norm:')
|
|||
|
print(' ||(a0 , a1)||∞ ≤ 0x' + str(maxInfNorm.hex()))
|
|||
|
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
|
|||
|
|
|||
|
# Contrary to Faz2013 paper, we use the max infinity norm
|
|||
|
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
|
|||
|
# which fails for some inputs
|
|||
|
#
|
|||
|
# +1 for signed column
|
|||
|
# Optional +1 for handling negative miniscalars
|
|||
|
L = int(maxInfNorm).bit_length() + 1
|
|||
|
L += 1
|
|||
|
|
|||
|
def getGLV1_decomp(scalar):
|
|||
|
|
|||
|
maxLen = (int(r).bit_length() + 1) // 2 + 1
|
|||
|
|
|||
|
a0 = (v[0] * scalar) >> n
|
|||
|
a1 = (v[1] * scalar) >> n
|
|||
|
|
|||
|
k0 = scalar - a0 * Lat[0][0] - a1 * Lat[1][0]
|
|||
|
k1 = 0 - a0 * Lat[0][1] - a1 * Lat[1][1]
|
|||
|
|
|||
|
assert int(k0).bit_length() <= maxLen
|
|||
|
assert int(k1).bit_length() <= maxLen
|
|||
|
|
|||
|
assert scalar == (k0 + k1 * (lambda1_r % r)) % r
|
|||
|
assert scalar == (k0 + k1 * (lambda2_r % r)) % r
|
|||
|
|
|||
|
return k0, k1
|
|||
|
|
|||
|
def recodeScalars(k):
|
|||
|
m = 2
|
|||
|
|
|||
|
b = [[0] * L, [0] * L]
|
|||
|
b[0][L-1] = 0
|
|||
|
for i in range(0, L-1): # l-2 inclusive
|
|||
|
b[0][i] = 1 - ((k[0] >> (i+1)) & 1)
|
|||
|
for j in range(1, m):
|
|||
|
for i in range(0, L):
|
|||
|
b[j][i] = k[j] & 1
|
|||
|
k[j] = k[j]//2 + (b[j][i] & b[0][i])
|
|||
|
|
|||
|
return b
|
|||
|
|
|||
|
def buildLut(P0, P1):
|
|||
|
m = 2
|
|||
|
lut = [0] * (1 << (m-1))
|
|||
|
lut[0] = P0
|
|||
|
lut[1] = P0 + P1
|
|||
|
return lut
|
|||
|
|
|||
|
def pointToString(P):
|
|||
|
(Px, Py, Pz) = P
|
|||
|
return '(x: ' + Integer(Px).hex() + ', y: ' + Integer(Py).hex() + ', z: ' + Integer(Pz).hex() + ')'
|
|||
|
|
|||
|
def scalarMulEndo(scalar, P0):
|
|||
|
m = 2
|
|||
|
|
|||
|
print('L: ' + str(L))
|
|||
|
|
|||
|
print('scalar: ' + Integer(scalar).hex())
|
|||
|
|
|||
|
k0, k1 = getGLV1_decomp(scalar)
|
|||
|
print('k0: ' + k0.hex())
|
|||
|
print('k1: ' + k1.hex())
|
|||
|
|
|||
|
P1 = (lambda1_r % r) * P0
|
|||
|
(Px, Py, Pz) = P0
|
|||
|
P1_endo = G1([Px*phi1 % p, Py, Pz])
|
|||
|
assert P1 == P1_endo
|
|||
|
|
|||
|
expected = scalar * P0
|
|||
|
decomp = k0*P0 + k1*P1
|
|||
|
assert expected == decomp
|
|||
|
|
|||
|
print('------ recode scalar -----------')
|
|||
|
even = k0 & 1 == 0
|
|||
|
if even:
|
|||
|
k0 += 1
|
|||
|
|
|||
|
b = recodeScalars([k0, k1])
|
|||
|
print('b0: ' + str(list(reversed(b[0]))))
|
|||
|
print('b1: ' + str(list(reversed(b[1]))))
|
|||
|
|
|||
|
print('------------ lut ---------------')
|
|||
|
|
|||
|
lut = buildLut(P0, P1)
|
|||
|
|
|||
|
print('------------ mul ---------------')
|
|||
|
# b[0][L-1] is always 0
|
|||
|
Q = lut[b[1][L-1]]
|
|||
|
for i in range(L-2, -1, -1):
|
|||
|
Q *= 2
|
|||
|
Q += (1 - 2 * b[0][i]) * lut[b[1][i]]
|
|||
|
|
|||
|
if even:
|
|||
|
Q -= P0
|
|||
|
|
|||
|
print('final Q: ' + pointToString(Q))
|
|||
|
print('expected: ' + pointToString(expected))
|
|||
|
assert Q == expected
|
|||
|
|
|||
|
# Test generator
|
|||
|
set_random_seed(1337)
|
|||
|
|
|||
|
for i in range(1):
|
|||
|
print('---------------------------------------')
|
|||
|
scalar = randrange(r) # Pick an integer below curve order
|
|||
|
P = G1.random_point()
|
|||
|
P = clearCofactorG1(P)
|
|||
|
scalarMulEndo(scalar, P)
|