├── LICENSE ├── README.md ├── fpverifiabledelayfunction.py ├── verifiabledelayfunction.py ├── pairing.py ├── curve.py ├── fp2verifiabledelayfunction.py ├── main.py └── point.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 isogenies-vdf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Isogenies VDF Paper 2 | 3 | https://eprint.iacr.org/2019/166.pdf 4 | 5 | # How to compute our VDF ? 6 | `sage -python3 vdf.py --protocol PROTOCOL --method METHOD --pSize PSIZE --nbIterations NBITERATIONS --loglevel LOGLEVEL` 7 | where: 8 | - `PROTOCOL` is `fp` or `fp2` 9 | - `METHOD` is `kernel4k` or `kernel4` 10 | - `PSIZE` is `p14-toy`, `p89-toy` or `p1506` 11 | - `NBITERATIONS` is the number of 2-isogenies to compute for the evaluation. 12 | - `LOGLEVEL` is the severity for the logging. 13 | 14 | # Issues. 15 | 16 | - How to add two points in x-model montgomery ? (in order to compute the Trace at 17 | the end of vdf_eval step. 18 | I fix it going to Weierstrass model and computing phi(Q) + frob(phi(Q)) and 19 | phi(Q) - frob(phi(Q)) because maybe there is a problem of sign on y. I find 20 | the right one checking if the coordinates lie in Fp. 21 | - One pairing on the verify step can be done before the answer of vdf_eval. 22 | - Do I really need all the a-coefficients of the steps in the output of setup ? 23 | When I compute the evaluation step, I need to switch to the right isomorphic 24 | curve and so need the a coefficient. 25 | Maybe it can be recomputed during eval... (?) 26 | -------------------------------------------------------------------------------- /fpverifiabledelayfunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | from sage.rings.integer_ring import ZZ 4 | from copy import copy 5 | import curve 6 | from point import Point 7 | import pairing 8 | from verifiabledelayfunction import VerifiableDelayFunction 9 | 10 | class FpVerifiableDelayFunction(VerifiableDelayFunction): 11 | def __init__(self, method, curve, delay): 12 | #change it with super().__init__(method, strategy, curve, delay) 13 | # For the moment, python3 does not work... 14 | self.method = method 15 | self.curve = curve 16 | Delta = delay 17 | while Delta % (curve.n-2) != 0 or (Delta // (curve.n-2)) % 2 != 0: 18 | Delta += 1 19 | self.delay = Delta 20 | # this is the strategy computation given in Luca De Feo's answer on crypto.stackexchange.com 21 | # it could be hard-coded. 22 | S = { 1: [] } 23 | C = { 1: 0 } 24 | p = 1 25 | q = 1 26 | nbIsog = (curve.n-2)//2 27 | for i in range(2, nbIsog+2): 28 | b, cost = min(((b, C[i-b] + C[b] + b*p + (i-b)*q) for b in range(1,i)), key=lambda t: t[1]) 29 | S[i] = [b] + S[i-b] + S[b] 30 | C[i] = cost 31 | self.strategy = S[nbIsog+1] 32 | 33 | def setup(self) : 34 | P = self.curve.pairing_group_random_point(extension_degree=1, twist=True) 35 | logging.debug('P = %s', str(P)) 36 | return [P] + self.setup_walk(1, P, stop=1, conditionP4=False) 37 | 38 | def evaluate(self, Q, dualKernels): 39 | ''' 40 | INPUT: 41 | * Q the second point of the protocol 42 | * dualKernels from the setup 43 | OUTPUT: 44 | * hat_phiQ the image of Q by the dual walk 45 | ''' 46 | return self.evaluation_walk(Q, dualKernels, 1) 47 | 48 | def verify(self, P, phiP, Q, hat_phiQ) : 49 | ''' 50 | INPUT: 51 | * P the first point of the protocol 52 | * phiP the image of P 53 | * Q the second point of the protocol 54 | * hat_phiQ dual image of Q 55 | OUTPUT: 56 | * true/false depending on the verification 57 | ''' 58 | 59 | if not(hat_phiQ.in_curve() and hat_phiQ.is_prime_order_point(self.curve.N)) : 60 | raise RuntimeError('evaluation step does not give point of the curve of order N') 61 | 62 | # this does not depend on the eval answer, and can be computed before the eval 63 | P_ws = P.weierstrass() 64 | phiP_ws = phiP.weierstrass() 65 | #this needs to be computed here 66 | Q_ws = Q.weierstrass() 67 | hat_phiQ_ws = hat_phiQ.weierstrass() 68 | 69 | logging.debug('No denominator computed.') 70 | 71 | _Z, mil11 = pairing.miller(hat_phiQ_ws, P_ws, ZZ(self.curve.N), denominator=False) 72 | e1 = pairing.exponentiation(self.curve, mil11[0]/mil11[1]) 73 | logging.debug('f_{N, hatphiQ}(P) = %s', str(mil11)) 74 | logging.debug('e(hatphiQ, P) = %s', str(e1)) 75 | 76 | 77 | _Z, mil22 = pairing.miller(Q_ws, phiP_ws, ZZ(self.curve.N), denominator=False) 78 | e2 = pairing.exponentiation(self.curve, mil22[0]/mil22[1]) 79 | logging.debug('f_{N, Q}(phiP) = %s', str(mil22)) 80 | logging.debug('e(Q, phiP) = %s', str(e2)) 81 | 82 | if e1 != 1 : 83 | if e1 == e2 : 84 | return True 85 | if e1 == 1/e2: 86 | return True 87 | # Pairing equation does not hold 88 | logging.debug('Pairing equation does not hold.') 89 | return False 90 | # e_hat_phiQ_P = 1 91 | logging.debug('e(HatPhiQ, P) = 1.') 92 | return False 93 | -------------------------------------------------------------------------------- /verifiabledelayfunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from copy import copy 3 | 4 | class VerifiableDelayFunction: 5 | def __init__(self, method, strategy, curve, delay): 6 | self.method = method 7 | self.strategy = strategy 8 | self.curve = curve 9 | self.delay = delay 10 | 11 | def __repr__(self): 12 | return 'Verifiable delay function with delay ' + repr(self.delay) 13 | 14 | __str__ = __repr__ 15 | 16 | def setup_walk(self, extensionDegree, P, stop, conditionP4=True) : 17 | ''' 18 | INPUT: 19 | * extensionDegree for the extension of the field where we choose points for the walk 20 | * P a point of self.curve for which we want the image 21 | * conditionP4 a boolean if we want to look at P4.x = +/- 1 22 | OUTPUT: 23 | * phiP the image of P by the random isogeny 24 | * dualKernels the list of the dual isogenies kernels 25 | REMARK: 26 | * Do not walk to the j=0,1728 curve at first step! The formulas of [4]-isogenies for these curves do not work for the moment. 27 | ''' 28 | # Using isogeny_degree4k, we compute k steps in a row 29 | k = len(self.strategy) 30 | # We need to do delay // k steps 31 | assert self.delay % k == 0 32 | nbSteps = self.delay // k 33 | 34 | c = copy(self.curve) 35 | images = [P] 36 | dualKernels = [] 37 | 38 | first = True 39 | 40 | for i in range(nbSteps) : 41 | if first : 42 | # we check the first step does not go to j=0 or 1728 curve 43 | j = 0 44 | while j == 0 or j == 1728 : 45 | kernel = c.power_of_2_order_random_point(2*k, extensionDegree, False) 46 | P4 = kernel.get_P4(k) 47 | xP4 = P4.normalize().x 48 | while (xP4 == 1 or xP4 == -1) and conditionP4 : 49 | kernel = c.power_of_2_order_random_point(2*k, extensionDegree, False) 50 | P4 = kernel.get_P4(k) 51 | xP4 = P4.normalize().x 52 | curve_onestep = P4.isogeny_degree4(images)[0].curve 53 | j = curve_onestep.weierstrass().j_invariant() 54 | first = False 55 | else : 56 | # kernel defines an isoeny of degree 4**k 57 | kernel = c.power_of_2_order_random_point(2*k, extensionDegree, False) 58 | P4 = kernel.get_P4(k) 59 | xP4 = P4.normalize().x 60 | while (xP4 == 1 or xP4 == -1) and conditionP4 : 61 | kernel = c.power_of_2_order_random_point(2*k, extensionDegree, False) 62 | P4 = kernel.get_P4(k) 63 | xP4 = P4.normalize().x 64 | # the image of dual_kernel will define the dual isogeny 65 | dual_kernel = kernel.dual_kernel_point(k) 66 | images.append(dual_kernel) 67 | images = kernel.isogeny_degree4k(images, self.strategy, stop) 68 | c = images[0].curve 69 | dualKernels.append(images.pop()) 70 | phiP = images[0] 71 | dualKernels = dualKernels[::-1] 72 | return [phiP, dualKernels] 73 | 74 | def evaluation_walk(self, Q, dualKernels, stop): 75 | ''' 76 | INPUT: 77 | * Q the second point of the protocol 78 | * dualKernels from the setup 79 | OUTPUT: 80 | * hat_phiQ the image of Q by the dual walk 81 | ''' 82 | k = len(dualKernels) 83 | T = Q 84 | c_t = copy(Q.curve) 85 | 86 | # TODO this is not efficient. 87 | for R in dualKernels : 88 | R = R.change_iso_curve(T.curve.a) 89 | [T] = R.isogeny_degree4k([T], self.strategy, stop) 90 | return T.change_iso_curve(self.curve.a) 91 | 92 | -------------------------------------------------------------------------------- /pairing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import curve 3 | from sage.rings.finite_rings.finite_field_constructor import GF 4 | from sage.rings.integer_ring import ZZ 5 | 6 | def eval_line(R, P, Q) : 7 | """ 8 | INPUT: 9 | * R a point of an EllipticCurve object 10 | * P a point of an EllipticCurve object 11 | * Q a point of an EllipticCurve object 12 | OUPUT: 13 | * The line through P and R evaluated at Q, given with the numerator and 14 | the denominator. 15 | * The point P+R 16 | """ 17 | if Q.is_zero(): 18 | raise ValueError("Q must be nonzero.") 19 | 20 | if P.is_zero() or R.is_zero(): 21 | if P == R: 22 | return [P, [P.curve().base_field().one(), 1]] 23 | if P.is_zero(): 24 | return [R, [Q[0] - R[0], 1]] 25 | if R.is_zero(): 26 | return [P, [Q[0] - P[0], 1]] 27 | elif P != R: 28 | if P[0] == R[0]: 29 | return [P.curve()(0), [Q[0] - P[0], 1]] 30 | else: 31 | lnum, lden = (R[1] - P[1]), (R[0] - P[0]) 32 | xPplusR = (lnum**2) - (lden**2)*(P[0] + R[0]) 33 | yPplusR = R[1]*(lden**3) + lnum*(xPplusR - (lden**2)*R[0]) 34 | zPplusR = lden**3 35 | PplusR = P.curve()(lden*xPplusR, -yPplusR, zPplusR) 36 | return [PplusR, [lden * (Q[1] - P[1]) - lnum * (Q[0] - P[0]), lden]] 37 | else: 38 | a1, a2, a3, a4, a6 = P.curve().a_invariants() 39 | numerator = (3*P[0]**2 + 2*a2*P[0] + a4 - a1*P[1]) 40 | denominator = (2*P[1] + a1*P[0] + a3) 41 | if denominator == 0: 42 | return [P.curve()(0), [Q[0] - P[0], 1]] #except in characteristic 2 ? 43 | else: 44 | #l = numerator/denominator 45 | x2P = numerator**2 - 2* P[0]*denominator**2 46 | y2P = P[1]*denominator**3 + numerator*(x2P - (denominator**2) *P[0]) 47 | z2P = denominator**3 48 | twoP = P.curve()(denominator*x2P, -y2P, z2P) 49 | return [twoP, [denominator * (Q[1] - P[1]) - numerator * (Q[0] - P[0]), denominator]] 50 | 51 | def miller(P, Q, n, denominator=False) : 52 | # return the miller loop f_{P, n}(Q) for an even embedded degree curve 53 | if Q.is_zero(): 54 | raise ValueError("Q must be nonzero.") 55 | n = ZZ(n) 56 | if n.is_zero(): 57 | raise ValueError("n must be nonzero.") 58 | n_is_negative = False 59 | if n < 0: 60 | n = n.abs() 61 | n_is_negative = True 62 | t_num, t_den = 1, 1 63 | V = P 64 | S = 2*V # V=P is in affine coordinates 65 | nbin = n.bits() 66 | i = n.nbits() - 2 67 | while i > -1: 68 | [S, [ell_num, ell_den]] = eval_line(V, V, Q) 69 | t_num = (t_num**2)*ell_num 70 | if denominator : 71 | [R, [vee_num, vee_den]] = eval_line(S, -S, Q) 72 | t_den = (t_den**2)*ell_den*vee_num 73 | t_num *= vee_den 74 | V = S 75 | if nbin[i] == 1: 76 | [S, [ell_num, ell_den]] = eval_line(V, P, Q) 77 | t_num = t_num*ell_num 78 | if denominator : 79 | [R, [vee_num, vee_den]] = eval_line(S, -S, Q) 80 | t_den *= ell_den*vee_num 81 | t_num *= vee_den 82 | V = S 83 | i = i-1 84 | if not(denominator) : 85 | t_den = 1 86 | if n_is_negative : 87 | t_num, t_den = t_den, t_num 88 | S = -S 89 | return [S, [t_num,t_den]] 90 | 91 | 92 | def exponentiation(curve, x) : 93 | """ 94 | INPUT: 95 | * x an element of Fp2 96 | OUTPUT: 97 | * x**((p**2-1)//N) in our special case where (p**2 - 1)//N == f* 2**(n+1) * (2**(n-1) * f * N - 1) 98 | """ 99 | x1 = (x**curve.f) 100 | for i in range(curve.n+1) : 101 | x1 = x1**2 102 | x2 = x1 103 | x3 = x2**(curve.f*curve.N) 104 | for i in range(curve.n-1) : 105 | x3 = x3**2 106 | return x3/x2 107 | -------------------------------------------------------------------------------- /curve.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import point 3 | from sage.rings.finite_rings.finite_field_constructor import GF 4 | from sage.schemes.elliptic_curves.constructor import EllipticCurve 5 | 6 | class Curve: 7 | def __init__(self, f, n, N, a, ext): 8 | self.f = f 9 | self.n = n 10 | self.N = N 11 | self.p = 2**n*N*f - 1 12 | self.Fp = GF(self.p) 13 | self.Fpx = self.Fp['x']; x = self.Fpx.gen() 14 | self.Fp2 = self.Fp.extension(x**2+ext, 'u') 15 | self.a = self.Fp2(a) 16 | self.cof_P = (self.p+1)//self.N 17 | 18 | def __repr__(self): 19 | return 'Montgomery curve defined by y^2 = x^3 + (' + repr(self.a) + ')*x^2 + x over ' + repr(self.Fp2) 20 | 21 | __str__ = __repr__ 22 | 23 | def random_point(self, k=1, twist=False) : 24 | ''' 25 | INPUT: 26 | * k = 1 or 2 if you want a point defined over Fp or Fp2 27 | OUTPUT: 28 | * [x,1] a random point on the montgomery curve, in the xz-model 29 | ''' 30 | F = self.Fp2 if k==2 else self.Fp 31 | x = F.random_element() 32 | #strange trick for generalize with twist 33 | while not(F(x**3+self.a*x**2+x).is_square()) != twist : 34 | x = F.random_element() 35 | #self is not the curve if twist=True, but anyway 36 | return point.Point(x, F(1), self) 37 | 38 | def weierstrass(self) : 39 | ''' 40 | INPUT: 41 | 42 | OUTPUT: 43 | * E the elliptic curve in Weierstrass model (y^2 = x^3+a*x+b) 44 | ''' 45 | return EllipticCurve(self.Fp2, [1-(self.a**2)/3, self.a*(2*(self.a**2)/9-1)/3]) 46 | 47 | def power_of_2_order_random_point(self, k, extension_field = 2, twist = False) : 48 | ''' 49 | INPUT: 50 | * k an integer 51 | * extension_field an integer 52 | * twist a boolean 53 | OUTPUT: 54 | * R a point of the curve or its twist, of order 2**k, defined over \F_{p^k}, given in the Montgomery model 55 | ''' 56 | if (k > self.n) : 57 | raise RuntimeError('there is no point of order 2^%d over Fp^%d' % (k, extension_field)) 58 | cof = (self.p+1) // (2**k) 59 | 60 | if extension_field == 1 : 61 | # E(Fp) \simeq ZZ / ((p+1)/2) ZZ \times ZZ / 2 ZZ 62 | # there is no point of order 2^curve.n 63 | if k == self.n : 64 | raise RuntimeError('impossible to get a point of order 2^%d over Fp' % k) 65 | else : 66 | # we need to divide by 2 the cofactor because of the ZZ / 2 ZZ part above 67 | cof = cof // 2 68 | R = cof * self.random_point(extension_field, twist) 69 | while not(R.is_power_of_2_order_point(k)) : 70 | R = cof * self.random_point(extension_field, twist) 71 | return R 72 | 73 | def pairing_group_random_point(self, extension_degree = 2, twist = False) : 74 | ''' 75 | INPUT: 76 | * extension_degree an integer 77 | * twist a boolean 78 | OUTPUT: 79 | * a point of order self.N defined over self.Fp^extension_degree defined over the curve or its twist. 80 | ''' 81 | R = self.cof_P * self.random_point(extension_degree, twist) 82 | while R.z == 0 : 83 | R = self.cof_P * self.random_point(extension_degree, twist) 84 | return R 85 | 86 | def getPointFromWeierstrass(self, P) : 87 | ''' 88 | INPUT: 89 | * P a point on the Weierstrass curve 90 | OUTPUT: 91 | * the corresponding point on the montgomery curve, in the xz-model 92 | ''' 93 | if P[2] == 0 : 94 | return point.Point(1, 0, self) 95 | if P[2] == 1 : 96 | return point.Point(P[0] - self.a/3, 1, self) 97 | X, Y, Z = P[0]/P[2], P[1]/P[2], 1 98 | return self.getPointFromWeierstrass([X,Y,Z]) 99 | 100 | -------------------------------------------------------------------------------- /fp2verifiabledelayfunction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | from sage.rings.integer_ring import ZZ 4 | from copy import copy 5 | import curve 6 | from point import Point 7 | import pairing 8 | from verifiabledelayfunction import VerifiableDelayFunction 9 | 10 | class Fp2VerifiableDelayFunction(VerifiableDelayFunction): 11 | def __init__(self, method, curve, delay): 12 | #change it with super().__init__(method, strategy, curve, delay) 13 | # For the moment, python3 does not work... 14 | self.method = method 15 | self.curve = curve 16 | # We choose delay = 2*x*curve.n 17 | Delta = delay 18 | while Delta % (curve.n) != 0 or (Delta // curve.n) % 2 != 0: 19 | Delta += 1 20 | self.delay = Delta 21 | # this is the strategy computation given in Luca De Feo's answer on crypto.stackexchange.com 22 | # it could be hard-coded. 23 | S = { 1: [] } 24 | C = { 1: 0 } 25 | p = 1 26 | q = 1 27 | nbIsog = (curve.n)//2 28 | for i in range(2, nbIsog+2): 29 | b, cost = min(((b, C[i-b] + C[b] + b*p + (i-b)*q) for b in range(1,i)), key=lambda t: t[1]) 30 | S[i] = [b] + S[i-b] + S[b] 31 | C[i] = cost 32 | self.strategy = S[nbIsog+1] 33 | 34 | def setup(self): 35 | P = self.curve.pairing_group_random_point(extension_degree=1, twist=True) 36 | logging.debug('P = %s', str(P)) 37 | return [P] + self.setup_walk(2, P, stop=0) 38 | 39 | def evaluate(self, Q, dualKernels): 40 | ''' 41 | INPUT: 42 | * Q the second point of the protocol 43 | * dualKernels from the setup 44 | OUTPUT: 45 | * Tr_hat_phiQ the list of the possible images of Q by the dual walk composed by Trace (4 possible because of sign pb) 46 | ''' 47 | #T = hatphi(Q) 48 | T = self.evaluation_walk(Q, dualKernels, 0) 49 | #Trace trick 50 | frob_T = Point(T.x**self.curve.p, T.z**self.curve.p, T.curve) 51 | #not efficient 52 | fQ_ws = frob_T.weierstrass() 53 | Q_ws = T.weierstrass() 54 | R1 = Q_ws + fQ_ws 55 | R2 = Q_ws - fQ_ws 56 | #the (+/-) point to return is the one defined over Fp :-) 57 | return self.curve.getPointFromWeierstrass(R1) if R1[0] in self.curve.Fp and R1[1] in self.curve.Fp else self.curve.getPointFromWeierstrass(R2) 58 | 59 | def verify(self, P, phiP, Q, Tr_hat_phiQ) : 60 | ''' 61 | INPUT: 62 | * P the first point of the protocol 63 | * phiP the image of P 64 | * Q the second point of the protocol 65 | * Tr_hat_phiQ the list of hat_phiQ + frob(hat_phiQ) and hat_phiQ - frob(hat_phiQ) 66 | OUTPUT: 67 | * true/false depending on the verification 68 | ''' 69 | 70 | if not(Tr_hat_phiQ.in_curve() and Tr_hat_phiQ.x in self.curve.Fp and Tr_hat_phiQ.z in self.curve.Fp) : 71 | raise RuntimeError('evaluation step does not give point of the curve defined over Fp') 72 | 73 | # this does not depend on the eval answer, can be computed before the eval 74 | P_ws = P.weierstrass() 75 | phiP_ws = phiP.weierstrass() 76 | #this needs to be computed here 77 | Q_ws = Q.weierstrass() 78 | Tr_hat_phiQ_ws = Tr_hat_phiQ.weierstrass() 79 | 80 | logging.debug('Denominator computed') 81 | 82 | _Z, mil11 = pairing.miller(Tr_hat_phiQ_ws, P_ws, ZZ(self.curve.N), denominator=True) 83 | e1 = pairing.exponentiation(self.curve, mil11[0]/mil11[1]) 84 | logging.debug('f_{N, TrhatphiQ}(P) = %s', str(mil11)) 85 | logging.debug('e(TrHatPhiQ, P) = %s', str(e1)) 86 | 87 | _Z, mil22 = pairing.miller(Q_ws, phiP_ws, ZZ(self.curve.N), denominator=True) 88 | e2_squared = pairing.exponentiation(self.curve, mil22[0]/mil22[1])**2 89 | logging.debug('f_{N, Q}(phiP) = %s', str(mil22)) 90 | logging.debug('e(Q, phiP)² = %s', str(e2_squared)) 91 | 92 | if e1 != 1 : 93 | if e1 == e2_squared : 94 | return True 95 | if e1 == 1/e2_squared: 96 | return True 97 | # Pairing equation does not hold 98 | logging.debug('Pairing equation does not hold.') 99 | return False 100 | # e_Tr_hat_phiQ_P = 1 101 | logging.debug('e(TrHatPhiQ, P) = 1.') 102 | return False 103 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | import point 4 | import curve 5 | import argparse 6 | from sage.rings.integer_ring import ZZ 7 | from sage.misc.misc import cputime 8 | from verifiabledelayfunction import VerifiableDelayFunction 9 | from fpverifiabledelayfunction import FpVerifiableDelayFunction 10 | from fp2verifiabledelayfunction import Fp2VerifiableDelayFunction 11 | 12 | parser = argparse.ArgumentParser() 13 | parser.add_argument("--protocol", type=str, default="fp", 14 | help="choose the VDF protocol to use") 15 | parser.add_argument("--method", type=str, default="kernel4k", 16 | help="choose the method to store the setup walk") 17 | parser.add_argument("--pSize", type=str, default="p14-toy", 18 | help="determine the size of the prime p to use") 19 | parser.add_argument("--nbIterations", type=int, default=12, 20 | help="set the number of iterations of the VDF") 21 | parser.add_argument("--loglevel", type=str, default="INFO", 22 | help="determines the severity for the logs") 23 | 24 | args = parser.parse_args() 25 | 26 | numeric_level = getattr(logging, (args.loglevel).upper(), None) 27 | if not isinstance(numeric_level, int): 28 | raise ValueError('Invalid log level: %s' % args.loglevel) 29 | #logging.basicConfig(filename='vdf.log', filemode='w', level=numeric_level, format='%(asctime)s %(message)s', datefmt='%d/%m/%Y %I:%M:%S %p') 30 | logging.basicConfig(filename='vdf.log', filemode='w', level=numeric_level, format='%(message)s') 31 | 32 | protocol = args.protocol 33 | method = args.method 34 | pSize = args.pSize 35 | nbIterations = int(args.nbIterations) 36 | 37 | if pSize == 'p14-toy' : 38 | f = 1 39 | n = 8 40 | N = 53 41 | a = 10088 42 | alpha = 1 43 | if pSize == 'p89-toy' : 44 | f = 1 45 | n = 64 46 | N = 27212093 47 | a = 6 48 | alpha = 1 49 | if pSize == 'p1506' : 50 | f = 63 51 | n = 1244 52 | N = 0xc0256a57b1434a4970e315e3e572ad7b6b6268ca27a1bc14a5ec8d6e8f46ab63 53 | a = 138931309558156184106311716917677778941761847991286360325642242809534952018704195842136094062347931842162775765708572232752796610393601192925341167860358529602430304979627494497048448960083384310735203052588819895230906248500388348984991092188849520120483947949612966752973461165325952933739065855693165670941141036576698048539586409219548698834122183984266610530679658299939991747759033936995784464828547439035421618098378714023855965416127212175477937 54 | alpha = 3 55 | 56 | c = curve.Curve(f, n, N, a, alpha) 57 | 58 | # The delay will depend on nbIterations *and n* 59 | Delta = nbIterations 60 | 61 | logging.info('Protocol: %s', protocol) 62 | logging.info('Method: %s', method) 63 | logging.info('Prime size: %s', pSize) 64 | logging.info('Number of steps: %s', str(Delta)) 65 | 66 | if protocol == 'fp' : 67 | VDF = FpVerifiableDelayFunction(method, c, Delta) 68 | else : 69 | VDF = Fp2VerifiableDelayFunction(method, c, Delta) 70 | 71 | time = cputime() 72 | SETUP = VDF.setup() 73 | time = cputime(time) 74 | print('setup timing: %.5f seconds.' % time) 75 | [P, phiP, dualKernels] = SETUP 76 | 77 | c2 = dualKernels[0].curve 78 | 79 | logging.info('Setup:\t\t\t\t%s seconds', str(time)) 80 | 81 | #Generating a point Q 82 | #NOT IN THE SAME SUBGROUP AS phiP !!! 83 | phiP_ws = phiP.weierstrass() 84 | 85 | e_phiP_Q = 1 86 | while e_phiP_Q == 1 : 87 | Q = c2.cof_P * c2.random_point(1 if protocol=='fp' else 2, False) 88 | while Q.z == 0 : 89 | Q = c2.cof_P * c2.random_point(1 if protocol=='fp' else 2, False) 90 | Q_ws = Q.weierstrass() 91 | e_phiP_Q = Q_ws.weil_pairing(phiP_ws, ZZ(c2.N)) 92 | 93 | #EVAL 94 | time = cputime() 95 | Tr_hat_phiQ = VDF.evaluate(Q, dualKernels) 96 | time = cputime(time) 97 | print('eval timing: %.5f seconds.' % time) 98 | logging.info('Evaluation:\t\t\t%s seconds', str(time)) 99 | 100 | #VERIFY 101 | time = cputime() 102 | ver = VDF.verify(P, phiP, Q, Tr_hat_phiQ) 103 | time = cputime(time) 104 | print('verif timing: %.5f seconds.' % time) 105 | logging.info('Verification:\t\t\t%s seconds', str(time)) 106 | 107 | print('###############') 108 | if ver : 109 | print('#verif OK :-)#') 110 | logging.info('\t\t\t\tVerification OK') 111 | else : 112 | print('#verif nOK :-(#') 113 | logging.info('\t\t\t\tVerification NOT OK') 114 | #logging.info(setup = %s', str(SETUP)) 115 | logging.info('Tr_hat_phiQ = %s', str(Tr_hat_phiQ)) 116 | 117 | print('###############') 118 | -------------------------------------------------------------------------------- /point.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sage.all 3 | import curve 4 | from sage.rings.integer_ring import ZZ 5 | from copy import copy 6 | from collections import deque 7 | from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism 8 | from sage.schemes.elliptic_curves.constructor import EllipticCurve 9 | from sage.functions.other import sqrt 10 | 11 | class Point: 12 | def __init__(self, x, z, c): 13 | self.x = x 14 | self.z = z 15 | self.curve = c 16 | 17 | def __str__(self): 18 | return '[' + str(self.x) + ', ' + str(self.z) + ']' 19 | 20 | def __repr__(self): 21 | return '[' + repr(self.x) + ', ' + repr(self.z) + ']' 22 | 23 | def compareXWithWeierstrass(self, other) : 24 | ''' 25 | INPUT: 26 | * other a point of an elliptic curve of SageMath object EllipticCurve 27 | OUTPUT: 28 | * True of False if self corresponds to a point on a Weierstrass curve with the same x-coordinate as other 29 | ''' 30 | field = self.curve.Fp2 31 | selfW = self.weierstrass() 32 | if selfW.curve() != other.curve() : 33 | # we are in an isomorphic curve 34 | # let's move to the right one 35 | a1, b1 = selfW.curve().a4(), selfW.curve().a6() 36 | C2 = EllipticCurve(field, [field(other.curve().a4().polynomial().list()), field(other.curve().a6().polynomial().list())]) 37 | iso = WeierstrassIsomorphism(E=selfW.curve(), F=C2) 38 | selfW = iso(selfW) 39 | x = field(selfW[0].polynomial().list()) 40 | z = field(selfW[2].polynomial().list()) 41 | X = field(other[0].polynomial().list()) 42 | Z = field(other[2].polynomial().list()) 43 | return x * Z == z * X 44 | 45 | def __eq__(self,other) : 46 | return self.x * other.z == self.z * other.x 47 | 48 | def normalize(self) : 49 | ''' 50 | INPUT: 51 | 52 | OUTPUT: 53 | * [x/z, 1] the normalized point representing P 54 | ''' 55 | if self.z == 0 : 56 | return Point(1, 0, self.curve) 57 | return Point(self.x/self.z, 1, self.curve) 58 | 59 | def in_curve(self, twist=False) : 60 | ''' 61 | INPUT: 62 | 63 | OUTPUT: 64 | * True/False wheter if P in E 65 | ''' 66 | if self.z == 0 : 67 | return True 68 | x = self.x/self.z 69 | return (x**3 + self.curve.a*x**2 + x).is_square() + twist == 1 70 | 71 | def weierstrass(self) : 72 | ''' 73 | INPUT: 74 | 75 | OUTPUT: 76 | * the point on the weierstrass curve corresponding to the montgomery curve defined with a 77 | ''' 78 | x = self.x 79 | z = self.z 80 | if z == 0 : 81 | return self.curve.weierstrass()([0,1,0]) 82 | xn = x/z 83 | # sage does not like finite fiels 84 | if not(xn in ZZ) : 85 | xn = self.curve.Fp2(xn.polynomial().list()) 86 | if not((xn**3+self.curve.a*xn**2 + xn).is_square()) : 87 | print('point on the twist') 88 | x_w = xn + self.curve.a/3 89 | return self.curve.weierstrass().lift_x(x_w) 90 | 91 | def equals(self, Q) : 92 | return self.x == Q.x and self.z == Q.z 93 | 94 | def dbl(self) : 95 | ''' 96 | INPUT: 97 | 98 | OUTPUT: 99 | * R = [2]P, in the xz-model 100 | ''' 101 | #eprint 2017/212 algo 2 102 | x = self.x 103 | z = self.z 104 | v1 = x+z 105 | v1 = v1**2 106 | v2 = x-z 107 | v2 = v2**2 108 | xR = v1*v2 109 | v1 = v1-v2 110 | v3 = ((self.curve.a+2)/4)*v1 111 | v3 = v3+v2 112 | zR = v1*v3 113 | return Point(xR, zR, self.curve) 114 | 115 | def add(self, Q, PmQ) : 116 | ''' 117 | INPUT: 118 | * Q a point of the curve 119 | * PmQ = P-Q 120 | OUTPUT: 121 | * R = P+Q, in the xz-model 122 | ''' 123 | xP, zP = self.x, self.z 124 | xQ, zQ = Q.x, Q.z 125 | xm, zm = PmQ.x, PmQ.z 126 | v0 = xP + zP 127 | v1 = xQ - zQ 128 | v1 = v1 * v0 129 | v0 = xP - zP 130 | v2 = xQ + zQ 131 | v2 = v2*v0 132 | v3 = v1+v2 133 | v3 = v3**2 134 | v4 = v1-v2 135 | v4 = v4**2 136 | xR = zm *v3 137 | zR = xm * v4 138 | return Point(xR, zR, self.curve) 139 | 140 | def __rmul__(self, k) : 141 | ''' 142 | INPUT: 143 | * k an integer 144 | OUTPUT: 145 | * R = [k]P, in the xz-model 146 | ''' 147 | if k == 0 : 148 | return Point(1, 0, self.curve) #Smith notation 149 | k = abs(k) # function does not care about sign(k) 150 | R0 = self 151 | R1 = R0.dbl() 152 | i=0 153 | R1mR0 = R0 154 | k_bits = ZZ(k).bits()[::-1] 155 | for i in range(1, len(k_bits)) : 156 | if k_bits[i] == 0 : 157 | [R0, R1] = [R0.dbl(), R0.add(R1, R1mR0)] 158 | else : 159 | [R0, R1] = [R0.add(R1, R1mR0), R1.dbl()] 160 | if R0.z == 0 : 161 | return Point(1, 0, self.curve) 162 | return R0 163 | 164 | def is_power_of_2_order_point(self, k) : 165 | ''' 166 | INPUT: 167 | * k an integer 168 | OUTPUT: 169 | * True if self is of order 2^k, False else. 170 | ''' 171 | return ((2**k) * self).z == 0 and (((2**k)//2) * self).z != 0 172 | 173 | def is_prime_order_point(self, N) : 174 | ''' 175 | INPUT: 176 | * k an integer 177 | OUTPUT: 178 | * True if self is of order N, False else. 179 | ''' 180 | return (N*self).z == 0 and self.z != 0 181 | 182 | def get_P4(self, k) : 183 | ''' 184 | INPUT: 185 | * self is a point of order 4**k of the curve 186 | * k an integer 187 | OUTPUT: 188 | * R = [4**(k-1)]P4powk that is a point of order 4 of the curve 189 | ''' 190 | R = self 191 | R_prec = R 192 | while R.z != 0 : 193 | R, R_prec = 4*R, R 194 | return R_prec 195 | 196 | def isogeny_degree4(self, Points) : 197 | ''' 198 | INPUT: 199 | * self is a point of order 4 defining the isogeny of degree 4 200 | * Points a list of points for which we want the images 201 | OUTPUT: 202 | * list_images the list of the images of the points to evaluate 203 | REMARKS: 204 | * Case with x-coordinate != ±1 from SIDH-spec.pdf 205 | * Case with x-coordinate = ±1 from eprint 2016/413 206 | (does not work for curves j=0,1728) 207 | ''' 208 | list_images = [] 209 | 210 | XP4 = self.normalize().x 211 | if XP4 != 1 and XP4 != -1 : 212 | aprime = 4*XP4**4 - 2 213 | curve_prime = copy(self.curve) 214 | curve_prime.a = aprime 215 | for R in Points : 216 | X, Z = R.x, R.z 217 | if Z != 0 : 218 | X = X/Z 219 | phiP_Xprime = -(X*XP4**2 + X -2*XP4) * X * (X*XP4 - 1)**2 220 | phiP_Zprime = (X - XP4)**2*(2*X*XP4 - XP4**2-1) 221 | else : 222 | phiP_Xprime = 1 223 | phiP_Zprime = 0 224 | list_images.append(Point(phiP_Xprime, phiP_Zprime, curve_prime)) 225 | else : 226 | aprime = 2*(self.curve.a+6) / (self.curve.a-2) 227 | curve_prime = copy(self.curve) 228 | curve_prime.a = aprime 229 | for R in Points : 230 | X, Z = R.x, R.z 231 | phiP_Xprime = (X+Z)**2 * (self.curve.a*X*Z + X**2 + Z**2) 232 | phiP_Zprime = (2-self.curve.a) * X * Z * (X-Z)**2 233 | list_images.append(Point(phiP_Xprime, phiP_Zprime, curve_prime)) 234 | return list_images 235 | 236 | def dual_kernel_point(self, k) : 237 | ''' 238 | INPUT: 239 | * self is a point of order 4**k defining the kernel of the isogeny 240 | * k an integer such that there is an isogeny of degree 4**k 241 | OUTPUT: 242 | * Q4k a point of order 4**k for which its image is the kernel of the dual isogeny 243 | REMARK: 244 | * See p.23 (Alice’s validation of Bob’s public key.) of https://eprint.iacr.org/2016/413.pdf for details 245 | ''' 246 | P4 = self.get_P4(k) 247 | P2 = 2*P4 248 | Q_subgroup = False 249 | while not(Q_subgroup) : 250 | Q4k = self.curve.power_of_2_order_random_point(2*k) 251 | Q4 = Q4k.get_P4(k) 252 | Q2 = 2*Q4 253 | Q_subgroup = (P2.x * Q2.z != Q2.x * P2.z and P2.x * Q2.z != - Q2.x * P2.z) 254 | #condition equivalent to ePQ**(4**k) == 1 and ePQ**((4**k)//2) != 1 255 | return Q4k 256 | 257 | def change_iso_curve(self, a) : 258 | """ 259 | INPUT: 260 | * a the montgomery a coefficient of the target curve 261 | OUTPUT: 262 | * the point P seen in the new curve 263 | """ 264 | a1 = self.curve.Fp2(self.curve.a) 265 | if (a**2 - 4) * (a1**2 - 3)**3 != (a1**2 - 4) * (a**2 - 3)**3: 266 | # i.e if 256*(a1**2 - 3)**3/(a1**2 - 4) != 256*(a**2 - 3)**3/(a**2 - 4) 267 | raise RuntimeError('the curves are not isomorphic.') 268 | E_a1 = EllipticCurve(self.curve.Fp2, [0,a1,0,1,0]) 269 | E_a = EllipticCurve(self.curve.Fp2, [0,a,0,1,0]) 270 | xP = self.x/self.z 271 | yP = sqrt(xP**3+a1*xP**2 + xP) 272 | 273 | P_ws = E_a1(xP, yP) 274 | iso = E_a1.isomorphism_to(E_a) 275 | curve_target = copy(self.curve) 276 | curve_target.a = a 277 | return Point(iso(P_ws)[0], 1, curve_target) 278 | 279 | def isogeny_degree4k(self, listOfPoints, strategy, stop=0) : 280 | ''' 281 | INPUT: 282 | * self the point defining the kernel of the isogeny, of degree 4**k 283 | * listOfPoints a list of points that we want to evaluate 284 | * strategy a string defining the strategy to adopt: it could be hardcoded. k = len(strategy) 285 | * stop an integer if we want to stop before the k-th 4-isogeny. 286 | OUTPUT: 287 | * images the list of the images of the points of listOfPoints 288 | REMARKS: 289 | * self needs to be such that [4**(k-1)] self has x-coordinate != +/- 1. 290 | ''' 291 | k = len(strategy) 292 | l = k 293 | i = 0 294 | images = listOfPoints 295 | queue1 = deque() 296 | queue1.append([k, self]) 297 | while len(queue1) != 0 and l > stop : 298 | [h, P] = queue1.pop() 299 | if h == 1 : 300 | queue2 = deque() 301 | while len(queue1) != 0 : 302 | [h, Q] = queue1.popleft() 303 | [Q] = P.isogeny_degree4([Q]) 304 | queue2.append([h-1, Q]) 305 | queue1 = queue2 306 | images = P.isogeny_degree4(images) 307 | l -= 1 308 | elif strategy[i] > 0 and strategy[i] < h : 309 | queue1.append([h, P]) 310 | P = 4**(strategy[i]) * P 311 | queue1.append([h-strategy[i], P]) 312 | i += 1 313 | else : 314 | raise RuntimeError('There is a problem in the isogeny computation.') 315 | return images 316 | 317 | """def isogeny_walk(self, curvesPath, kernelsOfBigSteps, strategy) : 318 | k = len(kernelsOfBigSteps) 319 | T = self 320 | c_t = copy(self.curve) 321 | 322 | # At the moment, isogeny codomain is "up to isomorphism". 323 | # From the kernels given in setup, I need to move them into the 324 | # right curve. 325 | # That is why `kernel4` is not efficient (change_iso_curve times 326 | # the number of steps in the walk on the graph. In kernel4k, it 327 | # is reduced by a factor 1000. 328 | for R in kernelsOfBigSteps : 329 | print 'step!' 330 | R = R.change_iso_curve(T.curve.a) 331 | [T, kernelPoint, listOfCurves] = R.isogeny_degree4k(T, method='withoutKernel', strategy=strategy) 332 | #elif self.method == 'kernel4' : 333 | # for c1 in curvesPath: 334 | # R = Point(1, 1, c1).change_iso_curve(T.curve.a) 335 | # [T] = R.isogeny_degree4([T]) 336 | return T.change_iso_curve(self.curve.a) 337 | 338 | """ 339 | --------------------------------------------------------------------------------