def main(ctx, **kwargs): """ \b ,-~~-.___. / | ' \\ ( ) 0 \_/-, ,----' ==== // / \-'~; /~~~(O) / __/~| / | =( _____| (_________| """ algo_args = kwargs.copy() algorithm = algo_args.pop('algorithm') algo_args.pop('benchmark') if algorithm == 'csidh': from sidh.csidh import CSIDH algo = CSIDH(**algo_args) elif algorithm == 'bsidh': from sidh.bsidh import BSIDH algo_args.pop('style') algo_args.pop('exponent') algo = BSIDH(**algo_args) else: click.echo('algorithm not implemented') raise Exit(1) kwargs['algo'] = algo ctx.meta['sidh.kwargs'] = attrdict(kwargs)
def __init__(self, curvemodel, prime, formula, tuned, multievaluation, verbose): self.params = attrdict(parameters['bsidh'][prime]) self.prime = prime self.tuned = tuned self.multievaluation = multievaluation self.verbose = verbose random = SystemRandom() if curvemodel == 'montgomery': self.curve = MontgomeryCurve(prime) self.A = self.curve.A self.fp = self.curve.fp else: self.curve = None raise NotImplemented if formula == 'hvelu': self.formula = Hvelu(self.curve, self.tuned, self.multievaluation) elif formula == 'tvelu': self.formula = Tvelu(self.curve) elif formula == 'svelu': self.formula = Svelu(self.curve, self.tuned, self.multievaluation) else: self.formula = None raise NotImplemented if self.formula is not None and self.curve is not None: self.gae = Gae(prime, self.tuned, self.curve, self.formula) else: self.gae = None raise NotImplemented
def __init__( self, curvemodel, prime, formula, style, tuned, exponent, multievaluation, verbose, ): self.curvemodel = curvemodel self.prime = prime self.style = style self._exponent = exponent self.tuned = tuned self.multievaluation = multievaluation self.fp = None self.params = attrdict(parameters['csidh'][prime]) self.params.update(self.params[style]) if self.curvemodel == 'montgomery': self.curve = MontgomeryCurve(prime, style) self.fp = self.curve.fp else: self.curve = None raise NotImplemented if formula == 'hvelu': self.formula = Hvelu(self.curve, self.tuned, self.multievaluation) elif formula == 'tvelu': self.formula = Tvelu(self.curve) elif formula == 'svelu': self.formula = Svelu(self.curve, self.tuned, self.multievaluation) if self.style == 'df': self.gae = Gae_df(prime, self.tuned, self.curve, self.formula) elif self.style == 'wd1': self.gae = Gae_wd1(prime, self.tuned, self.curve, self.formula) elif self.style == 'wd2': self.gae = Gae_wd2(prime, self.tuned, self.curve, self.formula) else: self.gae = NotImplemented
def csidh_parameters(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] A = algo.curve.A global_L = L = algo.params.L n = algo.params.n m = algo.params.m delta = algo.params.delta full_torsion_points = algo.curve.full_torsion_points set_parameters_velu = algo.formula.set_parameters_velu set_zero_ops = algo.fp.set_zero_ops get_ops = algo.fp.get_ops KPs = algo.formula.KPs xISOG = algo.formula.xISOG xEVAL = algo.formula.xEVAL sJ_list = algo.formula.sJ_list xMUL = algo.curve.xMUL if True: # T_p belongs to E[pi - 1] # T_m belongs to E[pi + 1] T_p, T_m = full_torsion_points(A) else: # T_m belongs to E[pi - 1] # T_p belongs to E[pi + 1] T_m, T_p = full_torsion_points(A) assert len(L) == n parameters = dict() for idx in range(0, n, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(0, n, 1): if i != idx: Tp = xMUL(Tp, A, i) # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and xEVAL if global_L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[idx] - 1) / 2.0)) c = int(floor((global_L[idx] - 1.0) / (4.0 * b))) b += 1 parameters[str(idx)] = [] for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1): b -= 1 c = int(floor((global_L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # KPs procedure set_zero_ops() KPs(Tp, A, idx) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xISOG set_zero_ops() B = xISOG(A, idx) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xEVAL bench set_zero_ops() Tm = xEVAL(T_m, A) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # assert(validate(B)) parameters[str(idx)].append( (b, c, t, total_cost[0] + total_cost[1])) if global_L[idx] == 3: parameters[str(idx)] = (0, 0, None, None) else: parameters[str(idx)] = min(parameters[str(idx)], key=lambda tup: tup[3]) print(parameters[str(idx)][0], parameters[str(idx)][1]) return attrdict(name='print-parameters', **locals())
def Gae_wd2(prime, tuned, curve, formula): fp = curve.fp L = global_L = curve.L n = parameters['csidh'][prime]['n'] m = parameters['csidh'][prime]['wd2']['m'] temporal_m = list(set(m)) exponent_of_two = curve.exponent_of_two random = SystemRandom() # random_key() implements an uniform random sample from [-m_1,m_1] x ... x -[m_n, m_n] random_key = lambda m=m: [random.randint(-m_i, m_i) for m_i in m] ''' security() inputs : the list M of maximum number of degree-(l_i) isogeny constructions to be performed, and the length of M output : bits of security that M brings ''' security = lambda M, n: sum(list([log(2 * M[i] + 1, 2) for i in range(n)])) # In order to achieve efficiency, the optimal strategies and their cost are saved in two global dictionaries (hash tables) S = {1: {}} # Initialization of each strategy C = {1: {}} # Initialization of the costs: 0. for i in range(n): S[1][tuple([L[i]])] = [] # Strategy with a list with only one element (a small odd prime number l_i) C[1][tuple([L[i]])] = ( formula.C_xISOG[i] - curve.C_xMUL[i] + 2.0 * numpy.array([4.0, 2.0, 6.0]) ) # For catching the weigth of horizontal edges of the form [(0,j),(0,j+1)] for i in range(2, n + 1): C[i] = {} S[i] = {} ''' dynamic_programming_algorithm(): inputs: the list of small odd primes to be processed and its length output: the optimal strategy and its cost of the input list of small odd primes ''' def pubkey(sk): C_out, L_out, R_out, S_out, r_out = strategy_block_cost( L[::-1], m[::-1]) if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): return GAE( curve.A, sk, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: return GAE(curve.A, sk, L_out, R_out, S_out, r_out, m) def dh(sk, pk): assert curve.validate(pk), "public key does not validate" C_out, L_out, R_out, S_out, r_out = strategy_block_cost( L[::-1], m[::-1]) if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): ss = GAE( pk, sk, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: ss = GAE(pk, sk, L_out, R_out, S_out, r_out, m) return ss def dynamic_programming_algorithm(L, n): nonlocal S, C # If the approach uses dummy operations, to set DUMMY = 2.0; # otherwise, to set DUMMY = 1.0 (dummy free approach); if len(L) != n: # If the list of prime numbers doesn't have size n, then we return [],-1 print( "error:\tthe list of prime numbers has different size from %d." % n) return [], -1 else: # Assuming #L = n, we proceed. get_neighboring_sets = lambda L, k: [ tuple(L[i:i + k]) for i in range(n - k + 1) ] # This function computes all the k-tuple: (l_1, l_2, ..., l_{k)), # (l_2, l_3, ..., l_{k+1)), ..., (l_{n-k}, l_{n-k+1, ..., l_{n)). for i in range(2, n + 1): for Tuple in get_neighboring_sets(L, i): if C[i].get(Tuple) is None: alpha = [ ( b, C[len(Tuple[:b])][Tuple[:b]] + C[ # Subtriangle on the right side with b leaves len(Tuple[b:])][Tuple[b:]] + 2.0 # Subtriangle on the left side with (i - b) leaves * sum([ curve.C_xMUL[global_L.index(t)] for t in Tuple[:b] ]) + 2.0 # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with b leaves * sum([ formula.C_xEVAL[global_L.index(t)] for t in Tuple[b:] ]) + 2.0 # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves * sum([ curve.C_xMUL[global_L.index(t)] for t in Tuple[b:] ]), # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves ) for b in range(1, i - 1) ] + [( i - 1, C[i - 1][Tuple[:(i - 1)]] + C[ # Subtriangle on the right side with (i - 1) leaves 1][Tuple[(i - 1):]] + 1.0 # Subtriangle on the left side with 1 leaf (only one vertex) * sum([ curve.C_xMUL[global_L.index(t)] for t in Tuple[:(i - 1)] ]) + 2.0 # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with 1 leaf * formula.C_xEVAL[global_L.index(Tuple[i - 1])] + 2.0 # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - 1) leaves * curve.C_xMUL[global_L.index( Tuple[i - 1] )], # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - 1) leaves )] b, C[i][Tuple] = min( alpha, key=lambda t: curve.measure(t[1]) ) # We save the minimal cost corresponding to the triangle with leaves Tuple S[i][Tuple] = ( [b] + S[i - b][Tuple[b:]] + S[b][Tuple[:b]] ) # We save the optimal strategy corresponding to the triangle with leaves Tuple return ( S[n][tuple(L)], C[n][tuple(L)] + curve.C_xMUL[global_L.index(L[0])] - 2.0 * numpy.array([4.0, 2.0, 6.0]), ) # The weight of the horizontal edges [(0,n-1),(0,n)] must be equal to C_xISOG[global_L.index(L[0])]. ''' evaluate_strategy(): inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, a list of two projective Montgomery x-coordinate torsion-(l_1 x ... l_n) points x(T_+) := XP/ZP and x(T_-) := XM/ZM, the list of small odd primes [l_1, l_2, ..., l_n], an strategy and length of the given list of small odd primes, maximum number of degree-l_i isogeny constructions, and the secret integer vector to be evaluated output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is E / <P>, the new maximum and current number of degree-l_i isogeny constructions to be performed after the strategy evaluation. NOTE: T_+ belongs to E[pi - 1] and T_- belongs to E[pi + 1]. In particular, P = [T_-, T_+] ''' def evaluate_strategy(E, P, L, strategy, n, m, e): v = list(m) u = list(e) ramifications = [] moves = [ 0 ] # moves: this list determines whether an isogeny construction must be performed k = 0 # k: current element of the strategy ramifications.append( list(P) ) # list of pair of points (T_-, T_+) such that T_- in E[\pi + 1] and T_+ in E[\pi - 1] E_i = list(E) for i in range(len(strategy)): pos = global_L.index( L[n - 1 - i]) # Current element of global_L to be required # Reaching the vertex (n - 1 - i, i) # Vertical edges prev = sum(moves) while (prev + strategy[k]) < (n - 1 - i): moves.append( strategy[k]) # Number of vertical edges to be performed T = list( [list(ramifications[-1][0]), list(ramifications[-1][1])]) for j in range(prev, prev + strategy[k], 1): T = list([ curve.xMUL(T[0], E_i, global_L.index(L[j])), curve.xMUL(T[1], E_i, global_L.index(L[j])), ]) ramifications.append(list([list(T[0]), list(T[1])])) prev += strategy[k] k += 1 # Vertical edges without ramifications s_i = sign(e[pos]) # Sign of e[pos] c_i = (s_i + 1) // 2 # Constant-swap of T_+ and T_- if prev < (n - 1 - i): moves.append( strategy[k] ) # Number of vertical edges (without ramifications) to be performed T = list( [list(ramifications[-1][0]), list(ramifications[-1][1])]) T[0][0], T[1][0] = fp.fp_cswap(T[0][0], T[1][0], c_i) T[0][1], T[1][1] = fp.fp_cswap(T[0][1], T[1][1], c_i) for j in range(prev, prev + strategy[k], 1): T[0] = list(curve.xMUL(T[0], E_i, global_L.index( L[j]))) # A single scalar multiplication is required T[0][0], T[1][0] = fp.fp_cswap(T[0][0], T[1][0], c_i) T[0][1], T[1][1] = fp.fp_cswap(T[0][1], T[1][1], c_i) ramifications.append(list([list(T[0]), list(T[1])])) prev += strategy[k] k += 1 # At this point, vertex (n - 1 - i, i) has been reached if (v[pos] > 0 ): # Maximum number of degree-l_{pos} isogeny constructions? # At this step, ramifications[-1] is the i-th leaf # Swap the torsion-(l_{n-j}) elliptic curve points on the current leaf # T_- <- ramifications[-1][0] # T_+ <- ramifications[-1][1] ramifications[-1][0][0], ramifications[-1][1][0] = fp.fp_cswap( ramifications[-1][0][0], ramifications[-1][1][0], c_i) # XT_+ <-> XT_- ramifications[-1][0][1], ramifications[-1][1][1] = fp.fp_cswap( ramifications[-1][0][1], ramifications[-1][1][1], c_i) # ZT_+ <-> ZT_- if curve.isinfinity(ramifications[-1][0]) == False: # T_+ or T_- ? # Root # Swap the torsion elliptic curve points on the current root ( ramifications[0][0][0], ramifications[0][1][0], ) = fp.fp_cswap(ramifications[0][0][0], ramifications[0][1][0], c_i) ( ramifications[0][0][1], ramifications[0][1][1], ) = fp.fp_cswap(ramifications[0][0][1], ramifications[0][1][1], c_i) # Dummy or NOT Dummy degree-(l_{n-1-i}) isogeny construction, that's the question? b_i = isequal[u[pos] == 0] ( ramifications[-1][0][0], ramifications[0][0][0], ) = fp.fp_cswap(ramifications[-1][0][0], ramifications[0][0][0], b_i) ( ramifications[-1][0][1], ramifications[0][0][1], ) = fp.fp_cswap(ramifications[-1][0][1], ramifications[0][0][1], b_i) if formula.name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if tuned: formula.set_parameters_velu( formula.sJ_list[pos], formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and formula.xEVAL if global_L[pos] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[pos] - 1) / 2.0)) c = int( floor((global_L[pos] - 1.0) / (4.0 * b))) formula.set_parameters_velu(b, c, pos) if (formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND): K = formula.KPs(ramifications[-1][0], E_i, pos) else: formula.KPs(ramifications[-1][0], E_i, pos) T = curve.xMUL(ramifications[-1][0], E_i, pos) else: K = formula.KPs(ramifications[-1][0], E_i, pos) ( ramifications[-1][0][0], ramifications[0][0][0], ) = fp.fp_cswap(ramifications[-1][0][0], ramifications[0][0][0], b_i) ( ramifications[-1][0][1], ramifications[0][0][1], ) = fp.fp_cswap(ramifications[-1][0][1], ramifications[0][0][1], b_i) # New isogeny construction C_i = formula.xISOG(E_i, pos) # Next, the horizontal edge [(0,i),(0,i+1)] is performed if formula.name == 'tvelu' or ( formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND): d_i = (global_L[pos] - 1) // 2 mask = isequal[(global_L[pos] == 3 )] # catching special case when l = 3 Z = formula.yADD(K[(d_i + mask) - 1], K[0], K[(d_i + mask) - 2]) # y([d_i + 1]K[0]) Z[0], K[d_i][0] = fp.fp_cswap(Z[0], K[d_i][0], mask ^ 1) Z[1], K[d_i][1] = fp.fp_cswap(Z[1], K[d_i][1], mask ^ 1) T = formula.yADD( K[d_i], K[d_i - 1], K[0]) # y([2*d_i + 1]K[0]) := y([l_i]K[0]) T = [ fp.fp_add(T[1], T[0]), fp.fp_sub(T[1], T[0]), ] # x([l_i]K[0]) ramifications[0][1] = curve.xMUL(ramifications[0][1], E_i, pos) T_prime = list(ramifications[0][1]) if formula.name == 'tvelu' or ( formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND): ramifications[0][0] = formula.xEVAL( ramifications[0][0], pos) ramifications[0][1] = formula.xEVAL( ramifications[0][1], pos) else: ramifications[0][0] = formula.xEVAL( ramifications[0][0], E_i) ramifications[0][1] = formula.xEVAL( ramifications[0][1], E_i) T[0], ramifications[0][0][0] = fp.fp_cswap( T[0], ramifications[0][0][0], b_i) T[1], ramifications[0][0][1] = fp.fp_cswap( T[1], ramifications[0][0][1], b_i) T_prime[0], ramifications[0][1][0] = fp.fp_cswap( T_prime[0], ramifications[0][1][0], b_i) T_prime[1], ramifications[0][1][1] = fp.fp_cswap( T_prime[1], ramifications[0][1][1], b_i) ( ramifications[0][0][0], ramifications[0][1][0], ) = fp.fp_cswap(ramifications[0][0][0], ramifications[0][1][0], c_i) ( ramifications[0][0][1], ramifications[0][1][1], ) = fp.fp_cswap(ramifications[0][0][1], ramifications[0][1][1], c_i) # The remainder horizontal edges are performed for j in range(1, len(moves) - 1, 1): ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) # T_- (or T_) T = curve.xMUL(ramifications[j][0], E_i, pos) if formula.name == 'tvelu' or ( formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND): ramifications[j][0] = formula.xEVAL( ramifications[j][0], pos) else: ramifications[j][0] = formula.xEVAL( ramifications[j][0], E_i) T[0], ramifications[j][0][0] = fp.fp_cswap( T[0], ramifications[j][0][0], b_i) T[1], ramifications[j][0][1] = fp.fp_cswap( T[1], ramifications[j][0][1], b_i) # T_+ or (T_+) ramifications[j][1] = curve.xMUL( ramifications[j][1], E_i, pos) T = list(ramifications[j][1]) if formula.name == 'tvelu' or ( formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND): ramifications[j][1] = formula.xEVAL( ramifications[j][1], pos) else: ramifications[j][1] = formula.xEVAL( ramifications[j][1], E_i) T[0], ramifications[j][1][0] = fp.fp_cswap( T[0], ramifications[j][1][0], b_i) T[1], ramifications[j][1][1] = fp.fp_cswap( T[1], ramifications[j][1][1], b_i) ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) C_i[0], E_i[0] = fp.fp_cswap(C_i[0], E_i[0], b_i ^ 1) C_i[1], E_i[1] = fp.fp_cswap(C_i[1], E_i[1], b_i ^ 1) v[pos] -= 1 u[pos] -= s_i * (b_i ^ 1) else: for j in range(0, len(moves) - 1, 1): ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) ramifications[j][1] = curve.xMUL( ramifications[j][1], E_i, pos) ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) else: # This branch only depends on randomness # At this step, ramifications[-1] is the i-th leaf ramifications[-1][0][0], ramifications[-1][1][0] = fp.fp_cswap( ramifications[-1][0][0], ramifications[-1][1][0], c_i) ramifications[-1][0][1], ramifications[-1][1][1] = fp.fp_cswap( ramifications[-1][0][1], ramifications[-1][1][1], c_i) if curve.isinfinity(ramifications[-1][0]) == False: for j in range(0, len(moves) - 1, 1): ramifications[j][0] = curve.xMUL( ramifications[j][0], E_i, pos) ramifications[j][1] = curve.xMUL( ramifications[j][1], E_i, pos) else: for j in range(0, len(moves) - 1, 1): ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) ramifications[j][1] = curve.xMUL( ramifications[j][1], E_i, pos) ( ramifications[j][0][0], ramifications[j][1][0], ) = fp.fp_cswap(ramifications[j][0][0], ramifications[j][1][0], c_i) ( ramifications[j][0][1], ramifications[j][1][1], ) = fp.fp_cswap(ramifications[j][0][1], ramifications[j][1][1], c_i) moves.pop() ramifications.pop() pos = global_L.index( L[0]) # Current element of global_L to be required s_i = sign(e[pos]) # Sign of e[pos] c_i = (s_i + 1) // 2 # Constant-swap of T_+ and T_- # T_+ or T_- ? # Root ramifications[0][0][0], ramifications[0][1][0] = fp.fp_cswap( ramifications[0][0][0], ramifications[0][1][0], c_i) ramifications[0][0][1], ramifications[0][1][1] = fp.fp_cswap( ramifications[0][0][1], ramifications[0][1][1], c_i) if curve.isinfinity(ramifications[0][0]) == False: if m[pos] > 0: b_i = isequal[e[pos] == 0] if formula.name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if tuned: formula.set_parameters_velu(formula.sJ_list[pos], formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and formula.xEVAL if global_L[pos] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[pos] - 1) / 2.0)) c = int(floor((global_L[pos] - 1.0) / (4.0 * b))) formula.set_parameters_velu(b, c, pos) formula.KPs(ramifications[0][0], E_i, pos) else: formula.KPs(ramifications[0][0], E_i, pos) C_i = formula.xISOG(E_i, pos) C_i[0], E_i[0] = fp.fp_cswap(C_i[0], E_i[0], b_i ^ 1) C_i[1], E_i[1] = fp.fp_cswap(C_i[1], E_i[1], b_i ^ 1) v[pos] -= 1 u[pos] -= s_i * (b_i ^ 1) return E_i, v, u ''' geometric_serie() inputs: and integer m, and a prime number l output: the nearest integer to l m x ----- l - 1 ''' def geometric_serie(m, l): l_float = float(l) m_float = float(m) return floor((m_float * l_float) / (l_float - 1.0) + 0.5) ''' filtered() inputs : a list L and a sublist SL of L output : L \\ SL ''' filtered = lambda List, sublist: [e for e in List if e not in sublist] ''' rounds() inputs : an integer vector (maximum number of isogeny constructions to be performed), and the length of the vector output : the subset of (indexes of) the small odd primes that determines the optimal strategy to be used, the number of times that each strategy will be used, and the complement of each subset (with respect to the set of all the small odd primes) ''' def rounds(e, n): tmp_N = range(n) tmp_e = list(e) rounds_out = [] sublists_L = [] sublists_C = [] while [e_i for e_i in tmp_e if e_i > 0] != []: e_min = min([e_i for e_i in tmp_e if e_i > 0]) rounds_out.append(e_min) sublists_L.append([i for i in tmp_N if tmp_e[i] >= e_min]) sublists_C.append(filtered(tmp_N, sublists_L[len(sublists_L) - 1])) tmp_e = [(tmp_e[i] - e_min) for i in tmp_N] return rounds_out, sublists_L, sublists_C ''' GAE(): inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, the secret integer vector to be evaluated, all the sublist of small odd primes to be required and their complement of each sublist, the (optimal) strategies corresponding to each sublist and their number of times to be evaluated, and the maximum number of degree-l_i isogeny constructions output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x corresponds with the image of the group action evaluation. NOTE: GAE comes from Group Action Evaluation, and the input sublists are determined by the output of function rounds(). THIS IS THE IMPLEMENTATION OF OUR PROPOSED STRATEGY METHOD. ''' def GAE(A, e, L, R, St, r, m): E_k = list(A) n = len(L) for j in range(0, n, 1): for k in range(0, r[j], 1): T_p, T_m = curve.elligator(E_k) for ii in range(0, exponent_of_two, 1): T_p = curve.xDBL(T_p, E_k) T_m = curve.xDBL(T_m, E_k) for l in R[j]: T_p = curve.xMUL(T_p, E_k, global_L.index(l)) T_m = curve.xMUL(T_m, E_k, global_L.index(l)) E_k, m, e = evaluate_strategy( E_k, list([list(T_m), list(T_p)]), L[j], St[j], len(L[j]), m, e, ) # Multiplicative strategy on the set of unreached small odd prime numbers unreached_sop = [global_L[i] for i in range(len(global_L)) if m[i] > 0] remainder_sop = [l for l in global_L if l not in unreached_sop] while len(unreached_sop) > 0: T_p, T_m = curve.elligator(E_k) for ii in range(0, exponent_of_two, 1): T_p = curve.xDBL(T_p, E_k) T_m = curve.xDBL(T_m, E_k) for l in remainder_sop: T_p = curve.xMUL(T_p, E_k, global_L.index(l)) T_m = curve.xMUL(T_m, E_k, global_L.index(l)) current_n = len(unreached_sop) E_k, m, e = evaluate_strategy( E_k, list([list(T_m), list(T_p)]), unreached_sop, list(range(current_n - 1, 0, -1)), current_n, m, e, ) # If the maximum of degree-(l_k) has been reached then the current batch (and its complement) must be updated tmp_unreached = [ unreached_sop[k] for k in range(current_n) if m[global_L.index(unreached_sop[k])] > 0 ] tmp_remainder = [ unreached_sop[k] for k in range(current_n) if m[global_L.index(unreached_sop[k])] == 0 ] unreached_sop = list( tmp_unreached) # Removing elements from the batch remainder_sop = ( remainder_sop + tmp_remainder ) # Adding elements to the complement of the batch return E_k ###################################################################################################################### # Next functions are used for computing optimal bounds basis = numpy.eye(n, dtype=int) # Next function computes the expected cost of our approach by assuming we have full torsion points def strategy_block_cost(L, e): elligator_cost = numpy.array([7.0, 3.0, 10.0]) # Elligator cost mul_fp_by_four = (numpy.array([4.0, 2.0, 4.0]) * exponent_of_two ) # Cost of computing x([2^exponent_of_two]P) n = len(L) e_prime = [geometric_serie(e[k], L[k]) for k in range(n)] tmp_r, tmp_Ls, tmp_Cs = rounds(e_prime, n) C_e = numpy.array([0.0, 0.0, 0.0]) S_out = [] L_out = [] R_out = [] for j in range(len(tmp_r)): R_out.append([L[k] for k in tmp_Cs[j]]) L_out.append([L[k] for k in tmp_Ls[j]]) bo_C = 2.0 * sum( [curve.C_xMUL[global_L.index(L[k])] for k in tmp_Cs[j]]) S_tmp, go_C = dynamic_programming_algorithm( [L[k] for k in tmp_Ls[j]], len(tmp_Ls[j])) S_out.append(S_tmp) C_e = (C_e + (go_C + bo_C + elligator_cost + 2.0 * mul_fp_by_four) * tmp_r[j]) return C_e, L_out, R_out, S_out, tmp_r return attrdict(name='wd2', **locals())
def MontgomeryCurve(prime): A = parameters['bsidh']['A'] nm = parameters['bsidh'][prime]['nm'] np = parameters['bsidh'][prime]['np'] Lp = parameters['bsidh'][prime]['Lp'] Lm = parameters['bsidh'][prime]['Lm'] Ep = parameters['bsidh'][prime]['Ep'] Em = parameters['bsidh'][prime]['Em'] p = parameters['bsidh'][prime]['p'] fp = F_p2(p) SQR = 1.00 # In F_p, we have SQR_{F_p} = SQR x MUL_{F_p} ADD = 0.00 # In F_p, we have ADD_{F_p} = ADD x MUL_{F_p} measure = lambda x: (x[0] + SQR * x[1] + ADD * x[2]) def dacs(l, r0, r1, r2, chain): ''' dacs() inputs: a small odd prime number l, three integer numbers, and a list output: all the differential additions chains corresponding with the input l NOTE: this is a recursive approach ''' if r2 == l: return [(chain, r2)] elif r2 < l and len(chain) <= 1.5 * log(l, 2): return dacs(l, r0, r2, r2 + r0, chain + [1]) + dacs( l, r1, r2, r2 + r1, chain + [0]) else: return [] def sdac(l): ''' sdac() input: a small odd prime number l output: the shortest differential additions chains corresponding with the input l NOTE: this function uses a recursive function ''' all_dacs = dacs(l, 1, 2, 3, []) return min(all_dacs, key=lambda t: len(t[0]))[0] path = resource_filename('sidh', "data/sdacs/" + prime) SDACS = filename_to_list_of_lists_of_ints(path) if len(SDACS) == 0: print("// SDAC's to be computed") SDACS = list( map(sdac, Lp) ) # Shortest Differential Addition Chains for each small odd prime l in Lp # Next nm SDACS SDACS = SDACS + list( map(sdac, Lm) ) # Shortest Differential Addition Chains for each small odd prime l in Lm #print("// Storing SDAC's in a file") #write_list_of_lists_of_ints_to_file(path, SDACS) SDACS_LENGTH = list(map(len, SDACS)) ## if sys.argv[0] != 'header.py': ## print("// Shortest Differential Addition Chains (SDAC) for each l_i;") #try: # # List of Small odd primes, L := [l_0, ..., l_{n-1}] # f = open(sdacs_data + setting.prime) # if sys.argv[0] != 'header.py': # print("// SDAC's to be read from a file") # SDACS = [] # SDACS_LENGTH = [] # for i in range(0, np + nm, 1): # tmp = f.readline() # tmp = [int(b) for b in tmp.split()] # SDACS.append(tmp) # SDACS_LENGTH.append(len(tmp)) # f.close() #except IOError: # if sys.argv[0] != 'header.py': # print("// SDAC's to be computed") # # First np SDACS # SDACS = list( # map(sdac, Lp) # ) # Shortest Differential Addition Chains for each small odd prime l in Lp # SDACS_LENGTH = [len(sdacs) for sdacs in SDACS] # # Next nm SDACS # SDACS = SDACS + list( # map(sdac, Lm) # ) # Shortest Differential Addition Chains for each small odd prime l in Lm # SDACS_LENGTH = [len(sdacs) for sdacs in SDACS] # # Storing the SDACS to be used # print("// Storing SDAC's in a file") # f = open(sdacs_data + setting.prime, 'w') # for i in range(0, np + nm, 1): # f.writelines(' '.join([str(tmp) for tmp in SDACS[i]]) + '\n') # f.close() # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to include case l=2 and l=4] SIDp = reduce(lambda x, y: x + y, [[Lp[i]] * Ep[i] for i in range(0, np, 1)]) # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}] SIDm = reduce(lambda x, y: x + y, [[Lm[i]] * Em[i] for i in range(0, nm, 1)]) SID = list(SIDp + SIDm) n = len(SID) L = global_L = list(Lp + Lm) cMUL = lambda l: numpy.array([ 4.0 * (SDACS_LENGTH[global_L.index(l)] + 2), 2.0 * (SDACS_LENGTH[global_L.index(l)] + 2), 6.0 * (SDACS_LENGTH[global_L.index(l)] + 2) - 2.0, ]) C_xMUL = list(map(cMUL, global_L)) # list of the costs of each [l]P def coeff(A): ''' ---------------------------------------------------------------------- coeff() input : projective Montgomery constants A24 := A + 2C and C24 := 4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the affine Montgomery coefficient A/C ---------------------------------------------------------------------- ''' output = fp.fp2_add(A[0], A[0]) # (2 * A24) output = fp.fp2_sub(output, A[1]) # (2 * A24) - C24 C24_inv = fp.fp2_inv(A[1]) # 1 / (C24) output = fp.fp2_add(output, output) # 4*A = 2[(2 * A24) - C24] output = fp.fp2_mul(output, C24_inv) # A/C = 2[(2 * A24) - C24] / C24 return output def jinvariant(A): ''' ---------------------------------------------------------------------- jinvariant() input : projective Montgomery constants A24 := A + 2C and C24 := 4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the j-invariant of E ---------------------------------------------------------------------- ''' A4_squared = fp.fp2_add(A[0], A[0]) # (2 * A24) A4_squared = fp.fp2_sub(A4_squared, A[1]) # (2 * A24) - C24 A4_squared = fp.fp2_add(A4_squared, A4_squared) # 4*A = 2[(2 * A24) - C24] # Now, we have A = A' / C' := A4 / A[1] = (4*A) / (4*C) A4_squared = fp.fp2_sqr(A4_squared) # (A')^2 C4_squared = fp.fp2_sqr(A[1]) # (C')^2 t = fp.fp2_add(C4_squared, C4_squared) # 2 * [(C')^2] num = fp.fp2_add(C4_squared, t) # 3 * [(C')^2] num = fp.fp2_sub(A4_squared, num) # (A')^2 - 3 * [(C')^2] s = fp.fp2_sqr(num) # { (A')^2 - 3 * [(C')^2] }^2 num = fp.fp2_mul(num, s) # { (A')^2 - 3 * [(C')^2] }^3 C4_squared = fp.fp2_sqr(C4_squared) # (C')^4 den = fp.fp2_add(t, t) # 4 * [(C')^2] den = fp.fp2_sub(A4_squared, den) # (A')^2 - 4 * [(C')^2] den = fp.fp2_mul(den, C4_squared) # {(A')^2 - 4 * [(C')^2] } * [(C')^4] den = fp.fp2_inv(den) # 1 / {(A')^2 - 4 * [(C')^2] } * [(C')^4] num = fp.fp2_mul( num, den ) # j := { (A')^2 - 3 * [(C')^2] }^3 / {(A')^2 - 4 * [(C')^2] } * [(C')^4] num = fp.fp2_add(num, num) # 2*j num = fp.fp2_add(num, num) # 4*j num = fp.fp2_add(num, num) # 8*j num = fp.fp2_add(num, num) # 16*j num = fp.fp2_add(num, num) # 32*j num = fp.fp2_add(num, num) # 64*j num = fp.fp2_add(num, num) # 128*j num = fp.fp2_add(num, num) # 256*j return num def isinfinity(P): """ isinfinity(P) determines if x(P) := (XP : ZP) = (1 : 0) """ return (P[1][0] == 0) and (P[1][1] == 0) def areequal(P, Q): """ areequal(P, Q) determines if x(P) = x(Q) """ XPZQ = fp.fp2_mul(P[0], Q[1]) ZPXQ = fp.fp2_mul(P[1], Q[0]) return (XPZQ[0] == ZPXQ[0]) and (XPZQ[0] == ZPXQ[0]) def xDBL(P, A): ''' ---------------------------------------------------------------------- xDBL() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, and the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the projective Montgomery x-coordinate point x([2]P) ---------------------------------------------------------------------- ''' t_0 = fp.fp2_sub(P[0], P[1]) t_1 = fp.fp2_add(P[0], P[1]) t_0 = fp.fp2_sqr(t_0) t_1 = fp.fp2_sqr(t_1) Z = fp.fp2_mul(A[1], t_0) X = fp.fp2_mul(Z, t_1) t_1 = fp.fp2_sub(t_1, t_0) t_0 = fp.fp2_mul(A[0], t_1) Z = fp.fp2_add(Z, t_0) Z = fp.fp2_mul(Z, t_1) return [X, Z] def xADD(P, Q, PQ): ''' ---------------------------------------------------------------------- xADD() input : the projective Montgomery x-coordinate points x(P) := XP/ZP, x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ output: the projective Montgomery x-coordinate point x(P+Q) ---------------------------------------------------------------------- ''' a = fp.fp2_add(P[0], P[1]) b = fp.fp2_sub(P[0], P[1]) c = fp.fp2_add(Q[0], Q[1]) d = fp.fp2_sub(Q[0], Q[1]) a = fp.fp2_mul(a, d) b = fp.fp2_mul(b, c) c = fp.fp2_add(a, b) d = fp.fp2_sub(a, b) c = fp.fp2_sqr(c) d = fp.fp2_sqr(d) X = fp.fp2_mul(PQ[1], c) Z = fp.fp2_mul(PQ[0], d) return [X, Z] def xDBLADD(P, Q, PQ, A): """ Next function computes x([2]P) and x(P + Q) """ # In C-code implementations this can be optimized S = xADD(P, Q, PQ) T = xDBL(P, A) return T, S # Modificar esta parte para usar cadenas de addicion def xMUL(P, A, j): ''' ---------------------------------------------------------------------- xMUL() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and an positive integer j output: the projective Montgomery x-coordinate point x([L[j]]P) ---------------------------------------------------------------------- ''' P2 = xDBL(P, A) R = [P, P2, xADD(P2, P, P)] for i in range(SDACS_LENGTH[j] - 1, -1, -1): T = xADD(R[2], R[SDACS[j][i] ^ 1], R[SDACS[j][i]]) R[0] = list(R[SDACS[j][i] ^ 1]) R[1] = list(R[2]) R[2] = list(T) return R[2] def Ladder3pt(m, P, Q, PQ, A): """ Next function computes x(P + [m]Q) """ X0 = list([list(Q[0]), list(Q[1])]) X1 = list([list(P[0]), list(P[1])]) X2 = list([list(PQ[0]), list(PQ[1])]) t = 0x1 for i in range(0, bitlength(p), 1): # In C-code implementations this branch should be implemented with cswap's if t & m != 0: X0, X1 = xDBLADD(X0, X1, X2, A) else: X0, X2 = xDBLADD(X0, X2, X1, A) t <<= 1 return X1 def prime_factors(P, A, points): """ ---------------------------------------------------------------------- prime_factors() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and subset of |[0, n]| output: the projective Montgomery x-coordinate points x([(p+1) / l_0]P), x([(p+1) / l_1]P), ..., x([(p+1) / l_{n-1}]P). ---------------------------------------------------------------------- """ n = len(points) if n == 1: # In this recursion level we have an order-l point return [P] elif n > 0: # We proceed by applying a divide-and-conquer procedure h = n // 2 if h > 0: # 1st half first_half = [] second_P = P for j in range(h): second_P = xMUL(second_P, A, points[j]) first_half.append(points[j]) # 2nd half second_half = [] first_P = P for j in range(h, n): first_P = xMUL(first_P, A, points[j]) second_half.append(points[j]) return prime_factors(first_P, A, first_half) + prime_factors( second_P, A, second_half) return [] def random_affine_point(A): """ Random affine point (x,y) in E : y^2 = x^3 + Ax^2 + x """ return "Not implemented yet!" def difference_point(P, Q, A): """ Next function computes x(P - Q) giving two affine points P and Q """ return "Not implemented yet!" def isfull_order(seq): tmp = [not isinfinity(seq_i) for seq_i in seq] return reduce(lambda x, y: (x and y), tmp) def full_torsion_points(A): return "Not implemented yet!" def CrissCross(alpha, beta, gamma, delta): t_1 = fp.fp2_mul(alpha, delta) t_2 = fp.fp2_mul(beta, gamma) return fp.fp2_add(t_1, t_2), fp.fp2_sub(t_1, t_2) return attrdict(**locals())
def bsidh_test(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] p = algo.params.p np = algo.params.np Ep = algo.params.Ep nm = algo.params.nm Em = algo.params.Em global_L = algo.curve.L curve = algo.curve formula = algo.formula coeff = curve.coeff random = SystemRandom() print("p := 0x%X;" % p) print("fp := GF(p);") print("_<x> := PolynomialRing(fp);") print("fp2<i> := ext<fp | x^2 + 1>;") print("Pr<x> := PolynomialRing(fp2);") # Reading public generators points f = open(resource_filename('sidh', 'data/gen/' + setting.prime)) # x(PA), x(QA) and x(PA - QA) PQA = f.readline() PQA = [int(x, 16) for x in PQA.split()] PA = [list(PQA[0:2]), [0x1, 0x0]] QA = [list(PQA[2:4]), [0x1, 0x0]] PQA = [list(PQA[4:6]), [0x1, 0x0]] # x(PB), x(QB) and x(PB - QB) PQB = f.readline() PQB = [int(x, 16) for x in PQB.split()] PB = [list(PQB[0:2]), [0x1, 0x0]] QB = [list(PQB[2:4]), [0x1, 0x0]] PQB = [list(PQB[4:6]), [0x1, 0x0]] f.close() A = algo.A a = coeff(A) print("E := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (a[0], a[1])) S = [list(PA[0]), list(PA[1])] T = [list(QA[0]), list(QA[1])] ST = [list(PQA[0]), list(PQA[1])] for i in range(0, np, 1): for idx in range(0, Ep[i], 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) assert curve.isinfinity(S) assert curve.isinfinity(T) assert curve.isinfinity(ST) print("\n// Verifying torsion-(p + 1) points") print("// x([p + 1]PA) = (1:0)?\t", curve.isinfinity(S)) print("// x([p + 1]QA) = (1:0)?\t", curve.isinfinity(T)) print("// x([p + 1]PQA) = (1:0)?\t", curve.isinfinity(ST)) S = [list(PB[0]), list(PB[1])] T = [list(QB[0]), list(QB[1])] ST = [list(PQB[0]), list(PQB[1])] for i in range(np, np + nm, 1): for idx in range(0, Em[i - np], 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) assert curve.isinfinity(S) assert curve.isinfinity(T) assert curve.isinfinity(ST) print("\n// Verifying torsion-(p - 1) points") print("// x([p - 1]PB) = (1:0)?\t", curve.isinfinity(S)) print("// x([p - 1]QB) = (1:0)?\t", curve.isinfinity(T)) print("// x([p - 1]PQB) = (1:0)?\t", curve.isinfinity(ST)) # Case (p + 1) S = [list(PA[0]), list(PA[1])] T = [list(QA[0]), list(QA[1])] ST = [list(PQA[0]), list(PQA[1])] assert curve.isinfinity(S) == False assert curve.isinfinity(T) == False assert curve.isinfinity(ST) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) print("\n// Verifying orders") assert curve.isfull_order(curve.prime_factors(S, A, range(0, np, 1))) assert curve.isfull_order(curve.prime_factors(T, A, range(0, np, 1))) assert curve.isfull_order(curve.prime_factors(ST, A, range(0, np, 1))) print( "// PA is a full order point?\t", curve.isfull_order(curve.prime_factors(S, A, range(0, np, 1))), ) print( "// QA is a full order point?\t", curve.isfull_order(curve.prime_factors(T, A, range(0, np, 1))), ) print( "// QPA is a full order point?\t", curve.isfull_order(curve.prime_factors(ST, A, range(0, np, 1))), ) # Case (p - 1) S = [list(PB[0]), list(PB[1])] T = [list(QB[0]), list(QB[1])] ST = [list(PQB[0]), list(PQB[1])] assert curve.isinfinity(S) == False assert curve.isinfinity(T) == False assert curve.isinfinity(ST) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) print("\n// Verifying orders") assert curve.isfull_order(curve.prime_factors(S, A, range(np, np + nm, 1))) assert curve.isfull_order(curve.prime_factors(T, A, range(np, np + nm, 1))) assert curve.isfull_order(curve.prime_factors(ST, A, range(np, np + nm, 1))) print( "// PB is a full order point?\t", curve.isfull_order(curve.prime_factors(S, A, range(np, np + nm, 1))), ) print( "// QB is a full order point?\t", curve.isfull_order(curve.prime_factors(T, A, range(np, np + nm, 1))), ) print( "// QPB is a full order point?\t", curve.isfull_order(curve.prime_factors(ST, A, range(np, np + nm, 1))), ) # Three point ladder: case (p + 1) S = [list(PA[0]), list(PA[1])] T = [list(QA[0]), list(QA[1])] ST = [list(PQA[0]), list(PQA[1])] assert curve.isinfinity(S) == False assert curve.isinfinity(T) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) k = random.randint(0, p) R = curve.Ladder3pt(k, S, T, ST, A) # print("k := 0x%X;" % k) # print("boolR, R := IsPoint(E, (0x%X + i * 0x%X) / (0x%X + i * 0x%X));" % # (R[0][0], R[0][1], R[1][0], R[1][1])) T_p = [list(R[0]), list(R[1])] T_m = [list(S[0]), list(S[1])] print( "\n// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment, and using torsion-(p + 1) points" ) for idx in range(0, np, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(idx + 1, np, 1): Tp = curve.xMUL(Tp, A, i) print("// l:\t%7d |" % global_L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: formula.set_parameters_velu(formula.sJ_list[idx], formula.sI_list[idx], idx) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' # from example 4.12 of https://eprint.iacr.org/2020/341 These # paramters are required in KPs, xISOG, and xEVAL if global_L[idx] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(global_L[idx] - 1) / 2.0)) c = int(floor((global_L[idx] - 1.0) / (4.0 * b))) formula.set_parameters_velu(b, c, idx) formula.print_parameters_velu() # ------------------------------------------------------------- # KPs procedure formula.fp.fp.set_zero_ops() formula.KPs(Tp, A, idx) formula.fp.fp.show_ops("Kps", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xISOG formula.fp.fp.set_zero_ops() Tp[0], A[0] = formula.fp.fp2_cswap(Tp[0], A[0], global_L[idx] == 4) Tp[1], A[1] = formula.fp.fp2_cswap(Tp[1], A[1], global_L[idx] == 4) B = formula.xISOG(A, idx) Tp[0], A[0] = formula.fp.fp2_cswap(Tp[0], A[0], global_L[idx] == 4) Tp[1], A[1] = formula.fp.fp2_cswap(Tp[1], A[1], global_L[idx] == 4) formula.fp.fp.show_ops("xISOG", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xEVAL: kernel point determined by the next isogeny evaluation formula.fp.fp.set_zero_ops() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and global_L[idx] <= formula.HYBRID_BOUND) or (global_L[idx] == 4)): T_p = formula.xEVAL(T_p, idx) else: T_p = formula.xEVAL(T_p, A) # xEVAL bench formula.fp.fp.set_zero_ops() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and global_L[idx] <= formula.HYBRID_BOUND) or (global_L[idx] == 4)): T_m = formula.xEVAL(T_m, idx) else: T_m = formula.xEVAL(T_m, A) formula.fp.fp.show_ops("xEVAL", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (global_L[idx] + 2.0))) # assert(validate(B)) A = list(B) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) # print("assert(Random(B) * (p + 1) eq B!0);") # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1])) # print("assert(BOOL);") print( "\n// All the l_i's have been processed, output of xISOG corresponds with the given below" ) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) a = coeff(A) print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (a[0], a[1])) print("assert(Random(B) * (p + 1) eq B!0);") A = [[0x8, 0x0], [0x4, 0x0]] # Three point ladder: case (p - 1) S = [list(PB[0]), list(PB[1])] T = [list(QB[0]), list(QB[1])] ST = [list(PQB[0]), list(PQB[1])] assert curve.isinfinity(S) == False assert curve.isinfinity(T) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = curve.xMUL(S, A, i) T = curve.xMUL(T, A, i) ST = curve.xMUL(ST, A, i) k = random.randint(0, p) R = curve.Ladder3pt(k, S, T, ST, A) # print("k := 0x%X;" % k) # print("boolR, R := IsPoint(E, (0x%X + i * 0x%X) / (0x%X + i * 0x%X));" % # (R[0][0], R[0][1], R[1][0], R[1][1])) T_p = [list(R[0]), list(R[1])] T_m = [list(S[0]), list(S[1])] print( "\n// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment, and using torsion-(p - 1) points" ) for idx in range(np, np + nm, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(idx + 1, np + nm, 1): Tp = curve.xMUL(Tp, A, i) print("// l:\t%7d |" % global_L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: formula.set_parameters_velu(formula.sJ_list[idx], formula.sI_list[idx], idx) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' # from example 4.12 of https://eprint.iacr.org/2020/341 These # paramters are required in KPs, xISOG, and xEVAL if global_L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[idx] - 1) / 2.0)) c = int(floor((global_L[idx] - 1.0) / (4.0 * b))) formula.set_parameters_velu(b, c, idx) formula.print_parameters_velu() # ------------------------------------------------------------- # KPs procedure formula.fp.fp.set_zero_ops() formula.KPs(Tp, A, idx) formula.fp.fp.show_ops("Kps", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xISOG formula.fp.fp.set_zero_ops() B = formula.xISOG(A, idx) formula.fp.fp.show_ops("xISOG", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xEVAL: kernel point determined by the next isogeny evaluation formula.fp.fp.set_zero_ops() if setting.formula == 'tvelu' or ( setting.formula == 'hvelu' and global_L[idx] <= formula.HYBRID_BOUND): T_p = formula.xEVAL(T_p, idx) else: T_p = formula.xEVAL(T_p, A) # xEVAL bench formula.fp.fp.set_zero_ops() if setting.formula == 'tvelu' or ( setting.formula == 'hvelu' and global_L[idx] <= formula.HYBRID_BOUND): T_m = formula.xEVAL(T_m, idx) else: T_m = formula.xEVAL(T_m, A) formula.fp.fp.show_ops("xEVAL", 1.0, 0.0, False) t = formula.fp.fp.get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (global_L[idx] + 2.0))) # assert(validate(B)) A = list(B) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) # print("assert(Random(B) * (p + 1) eq B!0);") # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1])) # print("assert(BOOL);") print( "\n// All the l_i's have been processed, output of xISOG corresponds with the given below" ) a = coeff(A) print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (a[0], a[1])) print("assert(Random(B) * (p + 1) eq B!0);") # """ print( "\n\"If no errors were showed using magma calculator, then all experiments were successful passed!\";" ) print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n") """ from bsidh.poly_redc import * flen = random.randint(1, 32) glen = random.randint(1, 32) f = [ [random.randint(0, p - 1), random.randint(0, p - 1)] for i in range(0, flen, 1) ] g = [ [random.randint(0, p - 1), random.randint(0, p - 1)] for i in range(0, glen, 1) ] fg = poly_mul(f, flen, g, glen) print("f := ", f, ";") print("g := ", g, ";") print("fg := ", fg, ";") print("Pr![ fp2!fi : fi in f] * Pr![ fp2!gi : gi in g] eq Pr![ fp2!fgi : fgi in fg];"); """ return attrdict(name='bsidh-test', **locals())
def csidh_header(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m delta = algo.params.delta geometric_serie = algo.gae.geometric_serie rounds = algo.gae.rounds strategy_block_cost = algo.gae.strategy_block_cost basis = numpy.eye(n, dtype=int) measure = algo.curve.measure # ========================================================================== if len(set(m)) > 1: # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i) LABEL_m = 'different_bounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) LABEL_m = 'with_same_bounds' if setting.tuned: verb = '-suitable' else: verb = '-classical' try: # List of Small Odd Primes, L := [l_0, ..., l_{n-1}] m_prime = [geometric_serie(m[k], L[k]) for k in range(n)] r_out, L_out, R_out = rounds(m_prime[::-1], n) for j in range(0, len(r_out), 1): R_out[j] = list([L[::-1][k] for k in R_out[j]]) L_out[j] = list([L[::-1][k] for k in L_out[j]]) f = open(strategy_data + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) # print("// Strategies to be read from a file") S_out = [] for i in range(0, len(r_out), 1): tmp = f.readline() tmp = [int(b) for b in tmp.split()] S_out.append(tmp) f.close() except IOError: # print("// Strategies to be computed") C_out, L_out, R_out, S_out, r_out = strategy_block_cost( L[::-1], m[::-1]) f = open( strategy_data + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb, 'w', ) for i in range(0, len(r_out)): f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n') f.close() if (len(set(m)) == 1) or ((len(set(m)) == 2) and (0 in set(m))): L_out = list([L_out[0]]) R_out = list([R_out[0]]) S_out = list([S_out[0]]) r_out = list([r_out[0]]) # --- k = 3 # Number of rows (format of the list) # --- print("#ifndef _STRATEGIES_H_") print("#define _STRATEGIES_H_\n") print( "// This script assumes the C-code implementation has the list of Small Odd Primes (SOPs) stored such that l_0 < l_1 < ... < l_{n-1}" ) print("// Recall, the strategies process from small SOPs to large SOPs.\n") for i in range(len(r_out)): print( "// -----------------------------------------------------------------------------------------------------------------------------------" ) print("// Strategy number %d\n" % (i)) L_string = "static uint32_t L%d[] " % (i) R_string = "static uint32_t W%d[] " % (i) S_string = "static uint32_t S%d[] " % (i) printl( L_string, [L.index(l_i) for l_i in L_out[i]], len(L_out[i]) // k + 1, ) if R_out[i] != []: printl( R_string, [L.index(r_i) for r_i in R_out[i]], len(R_out[i]) // k + 1, ) else: print("static uint32_t W%d[1];" % i) if S_out[i] != []: printl(S_string, S_out[i], len(S_out[i]) // k + 1) else: print("static uint32_t S%d[1];" % i) print("\n") print( "// -----------------------------------------------------------------------------------------------------------------------------------" ) print( "// -----------------------------------------------------------------------------------------------------------------------------------" ) print("#define NUMBER_OF_DIFFERENT_STRATEGIES %d" % len(L_out)) print("") L_string = ( "static uint32_t *L_STRATEGY[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t") R_string = ( "static uint32_t *W_STRATEGY[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t") S_string = "static uint32_t *S[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t" tmp_sizes = "static uint32_t NUMBER_OF_PRIMES[] = {\n\t" tmp_round = "static uint8_t ROUNDS[] = {\n\t" for i in range(len(L_out) - 1): L_string = L_string + "L%d, " % (i) R_string = R_string + "W%d, " % (i) S_string = S_string + "S%d, " % (i) tmp_sizes = tmp_sizes + "%3d," % (len(L_out[i])) tmp_round = tmp_round + "%3d," % (r_out[i]) L_string = L_string + "L%d\n\t};" % (len(L_out) - 1) R_string = R_string + "W%d\n\t};" % (len(L_out) - 1) S_string = S_string + "S%d\n\t};" % (len(L_out) - 1) tmp_sizes = tmp_sizes + "%3d\n\t};" % (len(L_out[len(L_out) - 1])) tmp_round = tmp_round + "%3d\n\t};" % (r_out[len(L_out) - 1]) print( "// L_STRATEGY[i] determines the small odd primes l_i per each strategy" ) print(L_string) print("\n// W_STRATEGY[i] determines L \ L_STRATEGY[i]") print(R_string) print( "\n// S_STRATEGY[i] determines the optimal strategy for L_STRATEGY[i]") print(S_string) print("\n// Number of primes for each strategy") print(tmp_sizes) print("") print("// Number of rounds per each different strategy") print(tmp_round) print("") print("// Maximum number of degree-(l_i) isogeny constructions") printl("static uint8_t M[]", m, n // k + 1) STYLE_NAME = { 'wd2': 'OAYT-style', 'wd1': 'MCR-style', 'df': 'dummy-free-style', }[setting.style] print( "\n#endif /* required framework for the strategies to be used in CSIDH-%s using %s */" % (setting.prime[1:], STYLE_NAME)) return attrdict(name='header', **locals())
def Poly_mul(curve): fp = curve.fp # ------------------------------------------------------------------------ # Table of 2 raised to a negative power inverse_of_two = fp.fp2_inv([2, 0]) max_exp = 2 * int(floor(sqrt(curve.L[-1]))) negative_powers_of_two = dict() negative_powers_of_two[1] = [1, 0] j = 1 j_next = 1 for i in range(0, max_exp, 1): j_next *= 2 negative_powers_of_two[j_next] = fp.fp2_mul(negative_powers_of_two[j], inverse_of_two) j = j_next # ------------------------------------------------------------------------- # Here, a polynomial is represented as a list of integers. def print_poly(h, lenh): # Just a pretty way for printing polynomials print(sum([h[i] * (x**i) for i in range(0, lenh, 1)]), ";") return None # Karatsuba style multiplication of two polynomials def karatsuba_mul(f, flen, g, glen): if flen < glen: return poly_mul(g, glen, f, flen) # At this step, we ensure flen >= glen if [] == g or [] == f: # Multiplication return [] if glen == 1: # Multipication by a constant return [fp.fp2_mul(f[i], g[0]) for i in range(0, flen, 1)] # At this step, we ensure flen >= glen >= 2 if flen == 2: # Multiplication of linear polynomials over fp # Thus c(x) = (a * b)(x) is a quadratic polynomial c = list([[0, 0], [0, 0], [0, 0]]) c[0] = fp.fp2_mul(f[0], g[0]) # coeff of x^0 c[2] = fp.fp2_mul(f[1], g[1]) # coeff of x^2 f01 = fp.fp2_add(f[0], f[1]) g01 = fp.fp2_add(g[0], g[1]) c[1] = fp.fp2_mul(f01, g01) c[1] = fp.fp2_sub(c[1], c[0]) c[1] = fp.fp2_sub(c[1], c[2]) # coeff of x^1 return c if flen == 3: # f(x) is a quadratic polynomial if glen == 2: # g(x) is a linear polynomial c = list([[0, 0], [0, 0], [0, 0], [0, 0]]) # Thus c(x) = (f * g)(x) is a cubic polynomial c[0] = fp.fp2_mul(f[0], g[0]) # coeff of x^0 c[2] = fp.fp2_mul(f[1], g[1]) f01 = fp.fp2_add(f[0], f[1]) g01 = fp.fp2_add(g[0], g[1]) c[1] = fp.fp2_mul(f01, g01) c[1] = fp.fp2_sub(c[1], c[0]) c[1] = fp.fp2_sub(c[1], c[2]) # coeff of x^1 c[3] = fp.fp2_mul(f[2], g[1]) # coeff of x^3 f2g0 = fp.fp2_mul(f[2], g[0]) c[2] = fp.fp2_add(c[2], f2g0) # coeff of x^2 return c if glen == 3: # g(x) is a a quadratic polynomial karatsuba_0 = karatsuba_mul(f[:1], 1, g[:1], 1) # field multiplication karatsuba_1 = karatsuba_mul( f[1:flen], 2, g[1:glen], 2) # multiplication of two linear polynomials # Middle part f_01 = fp.fp2_add(f[0], f[1]) f_02 = fp.fp2_add(f[0], f[2]) g_01 = fp.fp2_add(g[0], g[1]) g_02 = fp.fp2_add(g[0], g[2]) t_01 = fp.fp2_mul(f_01, g_01) t_02 = fp.fp2_mul(f_02, g_02) l_coeff = fp.fp2_sub(t_01, karatsuba_0[0]) l_coeff = fp.fp2_sub(l_coeff, karatsuba_1[0]) q_coeff = fp.fp2_sub(t_02, karatsuba_0[0]) q_coeff = fp.fp2_sub(q_coeff, karatsuba_1[2]) q_coeff = fp.fp2_add(q_coeff, karatsuba_1[0]) return list(karatsuba_0 + [l_coeff, q_coeff] + karatsuba_1[1:]) else: # Multiplication of polynomials of degree >= 3 over fp nf = flen // 2 mf = flen - nf f_low = f[:nf] f_high = f[nf:flen] if glen <= nf: c0 = karatsuba_mul(f_low, nf, g[:glen], glen) c1 = karatsuba_mul(f_high, mf, g[:glen], glen) return (c0[:nf] + [ fp.fp2_add(c0[nf + i], c1[i]) for i in range(0, glen - 1, 1) ] + c1[(glen - 1):]) mg = glen - nf g_low = g[:nf] g_high = g[nf:glen] f_mid = [fp.fp2_add(f_low[i], f_high[i]) for i in range(0, nf, 1)] + f_high[nf:] fg_low = karatsuba_mul(f_low, nf, g_low, nf) fg_high = karatsuba_mul(f_high, mf, g_high, mg) if mg < mf: g_mid = [ fp.fp2_add(g_low[i], g_high[i]) for i in range(0, mg, 1) ] + g_low[mg:] fg_mid = poly_mul(f_mid, mf, g_mid, nf) else: g_mid = [ fp.fp2_add(g_low[i], g_high[i]) for i in range(0, nf, 1) ] + g_high[nf:] fg_mid = poly_mul(f_mid, mf, g_mid, mg) fg_mid = [ fp.fp2_sub(fg_mid[i], fg_high[i]) for i in range(0, mf + mg - 1, 1) ] + fg_mid[(mf + mg - 1):] fg_mid = [ fp.fp2_sub(fg_mid[i], fg_low[i]) for i in range(0, 2 * nf - 1, 1) ] + fg_mid[(2 * nf - 1):] return (fg_low[:nf] + [ fp.fp2_add(fg_low[nf + i], fg_mid[i]) for i in range(0, nf - 1, 1) ] + [fg_mid[nf - 1]] + [ fp.fp2_add(fg_mid[nf + i], fg_high[i]) for i in range(0, mf - 1) ] + fg_high[(mf - 1):]) def qring_mul(f, g, e): n = len(f) m = len(f[0]) if (n == 1) and (m <= 8): # The product f[1] * g[1] is directly computed in B = fp[x] / (x^m + 1) h = karatsuba_mul(f[0], m, g[0], m) # Polynomial multiplication # Next, we reduce h modulo (x^m + 1) h_0 = h[:m] h_1 = h[m:] h_1 = h_1 + [[0, 0]] * (len(h_0) - len(h_1)) return [[fp.fp2_sub(h_0[i], h_1[i]) for i in range(0, m, 1)]] elif (n == 1) and (m > 8): # We need to embed f[1] and g[1] in B[y]/(y^n2+1) being # B = Fp[x]/(x^m2+1) where n2, andm2 are computed as follows s = int(floor(log(m, 2))) // 2 m2 = 2**(s + 1) n2 = (2 * m) // m2 hm2 = m2 // 2 # notice that n2*hm2 = m # Now, F and G will corresponds with the images of f[1] and g[1], # respectively, in B2[y]/(y^n2+1); here, B2 = Fp[x]/(x^hm2+1). # Notice B2[y]/(y^n2+1) is 'contained' in B[y]/(y^n2+1) F = [] G = [] ind = -1 for j in range(0, n2, 1): Fj = [] Gj = [] for i in range(0, hm2, 1): ind += 1 Fj.append(f[0][ind]) Gj.append(g[0][ind]) for i in range(0, hm2, 1): Fj.append([0, 0]) Gj.append([0, 0]) F.append(Fj) G.append(Gj) # Next, we recursively multiply F and G but now in B[y]/(y^n2+1) = B[y]/(y^n2-x^m2) H = qring_mul(F, G, m2) # At this point, H is equal to n2*F*G, so we need to divide by n2 # in2 = fp.fp2_inv(n2) # This inverse can be omited (we only care about polynomials with the same roots) # H lives in B[y]/(y^n2+1), 'contained' in Fp[x,y]/(y^n2+1), so to recover # the corresponding element h in Fp[x][x^m+1], in H we substitute y by x^hm2. h = list([[0, 0]] * m) degy = 0 fp.fp2_op = {0: fp.fp2_add, 1: fp.fp2_sub} for j in range(0, n2, 1): deg = degy for i in range(0, m2, 1): # for a polynomial position (l,k) (that is position [j-1,i-1] when viewed # as sequences), deg is the degree of x^k*y^l after substitution, namely, # deg = l*hm2 + k. Sometimes we have deg>m-1, this is why case we have to # take residues (deg mod m) and also flip the sign of H(l,k) when deg>m-1 q, r = divmod(deg, m) # (-1)^q determines if fp.fp2_add or fp.fp2_sub will be needed hr = H[j][i] h[r] = fp.fp2_op[q % 2](h[r], hr) deg += 1 degy += hm2 # return [ [fp.fp2_mul(h[i], in2) for i in range(0, m, 1)] ] return [[ fp.fp2_mul(h[i], negative_powers_of_two[n2]) for i in range(0, m, 1) ]] # return [list(h)] else: # The strategy proceeds by spliting the degree-n problem over B=Fp[x]/(x^m+1) into # two degree-n/2 over B, and then each of the two problems is handled recursively # until reaching degree-0 products over B # However, f*g is to be computed in B[y]/(y^n-x^e) = B[y]/[(y^n2)^2-(x^e2)^2] n2 = n // 2 e2 = e // 2 F1, F2 = [], [] G1, G2 = [], [] fp.fp2_op = { 0: fp.fp2_add, 1: fp.fp2_sub, } # (-1)^q determines if fp.fp2_add or fp.fp2_sub will be needed for j in range(0, n2, 1): Fj1, Fj2 = [], [] Gj1, Gj2 = [], [] for i in range(0, m, 1): q, r = divmod(i - e2, m) sgn = q % 2 # --- Fj1.append(fp.fp2_op[sgn](f[j][i], f[n2 + j][r])) Gj1.append(fp.fp2_op[sgn](g[j][i], g[n2 + j][r])) # --- Fj2.append(fp.fp2_op[1 - sgn](f[j][i], f[n2 + j][r])) Gj2.append(fp.fp2_op[1 - sgn](g[j][i], g[n2 + j][r])) F1.append(Fj1) F2.append(Fj2) G1.append(Gj1) G2.append(Gj2) # Next, we recursively multiply F1 and G1, and also F2 and G2 H1 = qring_mul(F1, G1, e2) H2 = qring_mul(F2, G2, m + e2) h1, h2 = [], [] for j in range(0, n2, 1): h1j, h2j = [], [] for i in range(0, m, 1): q, r = divmod(i - m + e2, m) sgn = q % 2 h1j.append(fp.fp2_add(H1[j][i], H2[j][i])) h2j.append(fp.fp2_op[1 - sgn]([0, 0], fp.fp2_sub( H1[j][r], H2[j][r]))) h1.append(h1j) h2.append(h2j) return h1 + h2 def poly_mul(f, flen, g, glen): ff = list(f) + [[0, 0]] * (glen - flen) gg = list(g) + [[0, 0]] * (flen - glen) # assert(len(ff) == len(gg)) hlen = len(ff) # hlen > 0 implies `fast` multiplication in fp[x]/(x^n + 1) will be always used if hlen > 512: n = 2**len(bin(2 * hlen - 1)[2:]) fg_qring = qring_mul([ff + [[0, 0]] * (n - hlen)], [gg + [[0, 0]] * (n - hlen)], 0) # return [ fp.fp2_mul(fg_qring[0][i], in2) for i in range(0, flen + glen, 1) ] return fg_qring[0][:(flen + glen - 1)] else: # This branch is only for checking if karatsuba is better(?) return karatsuba_mul(f, flen, g, glen) # Next function computes f*g mod x^n but using the same idea from the C-code implementation of https://eprint.iacr.org/2020/341 def poly_mul_modxn(n, f, flen, g, glen): if flen < glen: return poly_mul_modxn(n, g, glen, f, flen) # ------------------------------------ # At this step, we ensure flen >= glen # We require f mod x^n and g mod x^n if glen > n: return poly_mul_modxn(n, f[:n], n, g[:n], n) if flen > n: return poly_mul_modxn(n, f[:n], n, g, glen) if n == 0: # This case return zero as the empty list, that is [0]*0 return [] if [] == g or [] == f: # Multiplication by cero return [[0, 0]] * n if n == 1: # Only the constant coefficients are required return [fp.fp2_mul(f[0], g[0])] if n >= (flen + glen - 1): # Karatsuba multiplication with no possible savings return poly_mul(f, flen, g, glen) + [[0, 0]] * (n - flen - glen + 1) # At this step we ensure n < (flen + glen - 1) if glen == 1: return [fp.fp2_mul(f[i], g[0]) for i in range(0, n, 1)] # Here, flen >= glen >= 2 if n == 2: # Multiplication of two linear polynomials modulo x^2 # And thus, the cuadratic coefficient is not required f0g0 = fp.fp2_mul(f[0], g[0]) f0g1 = fp.fp2_mul(f[0], g[1]) f1g0 = fp.fp2_mul(f[1], g[0]) return [f0g0, fp.fp2_add(f0g1, f1g0)] if n == 3: if glen == 2: # Multiplication modulo x^3 of a linear polynomial with another of degree at least 2 f0g0 = fp.fp2_mul(f[0], g[0]) f1g1 = fp.fp2_mul(f[1], g[1]) f01 = fp.fp2_add(f[0], f[1]) g01 = fp.fp2_add(g[0], g[1]) t01 = fp.fp2_mul(f01, g01) t01 = fp.fp2_sub(t01, f0g0) t01 = fp.fp2_sub(t01, f1g1) f2g0 = fp.fp2_mul(f[2], g[0]) return [f0g0, t01, fp.fp2_add(f1g1, f2g0)] if glen == 3: c00 = fp.fp2_mul(f[0], g[0]) # coeff of x^0 c11 = fp.fp2_mul(f[1], g[1]) f01 = fp.fp2_add(f[0], f[1]) g01 = fp.fp2_add(g[0], g[1]) c01 = fp.fp2_mul(f01, g01) c01 = fp.fp2_sub(c01, c00) c01 = fp.fp2_sub(c01, c11) f02 = fp.fp2_add(f[0], f[2]) g02 = fp.fp2_add(g[0], g[2]) c02 = fp.fp2_mul(f02, g02) c22 = fp.fp2_mul(f[2], g[2]) c02 = fp.fp2_sub(c02, c00) c02 = fp.fp2_sub(c02, c22) c02 = fp.fp2_add(c02, c11) return [c00, c01, c02] if n == 4 and glen == 4: # This special case can be used as the general case it is expensive. Maybe with anoth bound for n should fine S = n for i in range(0, n // 2, 1): S = S + n - 1 - 2 * i c = list([[0, 0]] * S) nf = n // 2 # floor of n/2 nc = -(-n // 2) # Ceiling of n/2 for i in range(0, n, 1): c[i] = fp.fp2_mul(f[i], g[i]) k = n for i in range(0, nf, 1): for j in range(0, n - 2 * i - 1, 1): c[k] = fp.fp2_mul(fp.fp2_add(f[i], f[i + j + 1]), fp.fp2_add(g[i], g[i + j + 1])) c[k] = fp.fp2_sub(c[k], fp.fp2_add(c[i], c[i + j + 1])) k = k + 1 c[n - 1] = list(c[0]) for i in range(1, nf, 1): for j in range(1, n - 2 * i, 1): c[n + 2 * i - 1 + j] = fp.fp2_add( c[n + 2 * i - 1 + j], c[(1 + i) * n - i**2 - 1 + j]) for i in range(1, nc, 1): c[n + 2 * i - 1] = fp.fp2_add(c[n + 2 * i - 1], c[i]) delta = n - 1 return c[delta:(n + delta)] # The goal is to divide the multiplication using karatsuba style multiplication # but saving multiplications (some higher monomials can be omited) l1 = n // 2 # floor(n / 2) l0 = n - l1 # ceil( n / 2) # We proceed by spliting f(x) as f0(x^2) + x * f1(x^2) f1len = flen // 2 # floor(flen / 2) f0len = flen - f1len # ceil( flen / 2) f0 = [list(f[2 * i + 0]) for i in range(0, f0len, 1)] f1 = [list(f[2 * i + 1]) for i in range(0, f1len, 1)] # We proceed by spliting g(x) as g0(x^2) + x * g1(x^2) g1len = glen // 2 # floor(glen / 2) g0len = glen - g1len # ceil( geln / 2) g0 = [list(g[2 * i + 0]) for i in range(0, g0len, 1)] g1 = [list(g[2 * i + 1]) for i in range(0, g1len, 1)] # Middle part like karatsuba f01 = [fp.fp2_add(f0[i], f1[i]) for i in range(0, f1len, 1)] + f0[f1len:] g01 = [fp.fp2_add(g0[i], g1[i]) for i in range(0, g1len, 1)] + g0[g1len:] # product f0 * g0 must has degree at most x^l0 n0 = f0len + g0len - 1 if n0 > l0: n0 = l0 fg_0 = poly_mul_modxn(n0, f0, f0len, g0, g0len) # product f01 * g01 must has degree at most x^l1 n01 = f0len + g0len - 1 if n01 > l1: n01 = l1 fg_01 = poly_mul_modxn(n01, f01, f0len, g01, g0len) # product f1 * g1 must has degree at most x^l1 n1 = f1len + g1len - 1 if n1 > l1: n1 = l1 fg_1 = poly_mul_modxn(n1, f1, f1len, g1, g1len) # Computing the middle part fg_01 = [fp.fp2_sub(fg_01[i], fg_0[i]) for i in range(0, n01, 1)] + fg_01[n01:] fg_01 = [fp.fp2_sub(fg_01[i], fg_1[i]) for i in range(0, n1, 1)] + fg_01[n1:] # Unifying the computations fg = [[0, 0]] * n for i in range(0, n0, 1): fg[2 * i] = list(fg_0[i]) for i in range(0, n01, 1): fg[2 * i + 1] = list(fg_01[i]) for i in range(0, n1 - 1, 1): fg[2 * i + 2] = fp.fp2_add(fg[2 * i + 2], fg_1[i]) if 2 * n1 < n: fg[2 * n1] = fp.fp2_add(fg[2 * n1], fg_1[n1 - 1]) return fg # Next funtion computes the central part polynomial of f * g mod (x^m - 1) # Next two functions are based on the work title "The Middle Product Algorithm, I." by G. Hanrot. M. Quercia, and P. Zimmermann def quasi_poly_mul_middle(g, glen, f, flen): assert glen == len(g) assert flen == len(f) if glen == 0: return [] if glen == 1: return [fp.fp2_mul(g[0], f[0])] glen0 = glen // 2 # floor(glen / 2) glen1 = glen - glen0 # ceil(glen / 2) A = poly_mul_middle( g[glen0:], glen1, [ fp.fp2_add(f[i], f[i + glen1]) for i in range(0, 2 * glen1 - 1, 1) ], 2 * glen1 - 1, ) if glen % 2 == 0: B = poly_mul_middle( [fp.fp2_sub(g[glen1 + i], g[i]) for i in range(0, glen0, 1)], glen0, f[glen1:(3 * glen1 - 1)], 2 * glen1 - 1, ) else: B = poly_mul_middle( [g[glen0]] + [fp.fp2_sub(g[glen1 + i], g[i]) for i in range(0, glen0, 1)], glen1, f[glen1:(3 * glen1 - 1)], 2 * glen1 - 1, ) C = poly_mul_middle( g[:glen0], glen0, [ fp.fp2_add(f[glen1 + i], f[2 * glen1 + i]) for i in range(0, 2 * glen0 - 1, 1) ], 2 * glen0 - 1, ) assert len(A) == glen1 assert len(B) == glen1 assert len(C) == glen0 return [fp.fp2_sub(A[i], B[i]) for i in range(0, glen1, 1) ] + [fp.fp2_add(C[i], B[i]) for i in range(0, glen0, 1)] # Next funtion computes the central part polynomial of f * g mod (x^m - 1) # This functions is an extension of quasi_poly_mul_middle() def poly_mul_middle(g, glen, f, flen): if (glen == (flen // 2)) and (flen % 2 == 0): # Here deg(f) = 2d - 1 and deg(g) = d - 1 return quasi_poly_mul_middle(g, glen, f[1:] + [[0, 0]], flen) elif (glen == ((flen // 2) + 1)) and (flen % 2 == 0): # Here deg(f) = 2d - 1 and deg(g) = d return quasi_poly_mul_middle(g, glen, [[0, 0]] + f, flen + 1) elif (glen == ((flen // 2) + 1)) and (flen % 2 == 1): # Here deg(f) = 2d and deg(g) = d return quasi_poly_mul_middle(g, glen, f, flen) else: # Unbalanced case when glen < floor(flen/2) and ceil(flen/2) < glen # This case only happens when using scaled remainder trees if glen == 0: return [] # low part F0len = flen - glen F0 = f[:F0len] F0 = F0[::-1] G = g[::-1] fg_low = poly_mul_modxn(glen - 1, F0, F0len, G, glen) fg_low = fg_low[::-1] # high part F1 = f[F0len:] fg_high = poly_mul_modxn(glen, F1, glen, g, glen) return [ fp.fp2_add(fg_low[i], fg_high[i]) for i in range(0, glen - 1, 1) ] + [fg_high[glen - 1]] # Next function computes the product of two polynomials g(x) annd f(x) such that # g(x) = x^dg * g(1/x) and f(x) = x^df * f(1/x) where deg g = dg, and deg f = df def poly_mul_selfreciprocal(g, glen, f, flen): if glen == 0 and flen == 0: return [] if glen == 1 and flen == 1: return [fp.fp2_mul(g[0], f[0])] if glen == 2 and flen == 2: h = [[0, 0]] * 3 h[0] = fp.fp2_mul(g[0], f[0]) h[1] = fp.fp2_add(h[0], h[0]) h[2] = list(h[0]) return h if glen == 3 and flen == 3: h = [[0, 0]] * 5 h[0] = fp.fp2_mul(g[0], f[0]) h[2] = fp.fp2_mul(g[1], f[1]) g01 = fp.fp2_add(g[0], g[1]) f01 = fp.fp2_add(f[0], f[1]) h[1] = fp.fp2_mul(g01, f01) h[2] = fp.fp2_add(h[2], h[0]) h[1] = fp.fp2_sub(h[1], h[2]) h[2] = fp.fp2_add(h[2], h[0]) h[3] = list(h[1]) h[4] = list(h[0]) return h if glen == 4 and flen == 4: h = [[0, 0]] * 7 h[0] = fp.fp2_mul(g[0], f[0]) h[3] = fp.fp2_mul(g[1], f[1]) g01 = fp.fp2_add(g[0], g[1]) f01 = fp.fp2_add(f[0], f[1]) h[2] = fp.fp2_mul(g01, f01) h[2] = fp.fp2_sub(h[2], h[0]) h[1] = fp.fp2_sub(h[2], h[3]) h[3] = fp.fp2_add(h[3], h[0]) h[3] = fp.fp2_add(h[3], h[3]) h[4] = list(h[2]) h[5] = list(h[1]) h[6] = list(h[0]) return h if glen == 5 and flen == 5: h = [[0, 0]] * 9 g10 = fp.fp2_sub(g[1], g[0]) f01 = fp.fp2_sub(f[0], f[1]) h[1] = fp.fp2_mul(g10, f01) g20 = fp.fp2_sub(g[2], g[0]) f02 = fp.fp2_sub(f[0], f[2]) h[2] = fp.fp2_mul(g20, f02) g21 = fp.fp2_sub(g[2], g[1]) f12 = fp.fp2_sub(f[1], f[2]) h[3] = fp.fp2_mul(g21, f12) h[0] = fp.fp2_mul(g[0], f[0]) g1f1 = fp.fp2_mul(g[1], f[1]) g2f2 = fp.fp2_mul(g[2], f[2]) t = fp.fp2_add(g1f1, h[0]) h[1] = fp.fp2_add(h[1], t) h[3] = fp.fp2_add(h[3], h[1]) h[4] = fp.fp2_add(t, g2f2) h[4] = fp.fp2_add(h[4], t) h[2] = fp.fp2_add(h[2], t) h[2] = fp.fp2_add(h[2], g2f2) h[3] = fp.fp2_add(h[3], g1f1) h[3] = fp.fp2_add(h[3], g2f2) h[5] = list(h[3]) h[6] = list(h[2]) h[7] = list(h[1]) h[8] = list(h[0]) return h if glen == flen and (glen % 2) == 1: # Case two degree polynomials of same even degree len0 = (glen + 1) // 2 len1 = glen // 2 g0 = [[0, 0]] * len0 f0 = [[0, 0]] * len0 g1 = [[0, 0]] * len1 f1 = [[0, 0]] * len1 # We proceed by applying the same idea as in poly_mul_modxn # --- for i in range(0, len0, 1): g0[i] = list(g[2 * i]) f0[i] = list(f[2 * i]) h0 = poly_mul_selfreciprocal(g0, len0, f0, len0) # --- for i in range(0, len1, 1): g1[i] = list(g[2 * i + 1]) f1[i] = list(f[2 * i + 1]) h1 = poly_mul_selfreciprocal(g1, len1, f1, len1) # --- for i in range(0, len1, 1): g0[i] = fp.fp2_add(g0[i], g1[i]) f0[i] = fp.fp2_add(f0[i], f1[i]) for i in range(0, len1, 1): g0[i + 1] = fp.fp2_add(g0[i + 1], g1[i]) f0[i + 1] = fp.fp2_add(f0[i + 1], f1[i]) h01 = poly_mul_selfreciprocal(g0, len0, f0, len0) # Mixing the computations for i in range(0, 2 * len0 - 1, 1): h01[i] = fp.fp2_sub(h01[i], h0[i]) for i in range(0, 2 * len1 - 1, 1): h01[i] = fp.fp2_sub(h01[i], h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 1] = fp.fp2_sub(h01[i + 1], h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 1] = fp.fp2_sub(h01[i + 1], h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 2] = fp.fp2_sub(h01[i + 2], h1[i]) for i in range(1, 2 * len0 - 1, 1): h01[i] = fp.fp2_sub(h01[i], h01[i - 1]) # Unifying the computations hlen = 2 * glen - 1 h = [[0, 0]] * hlen for i in range(0, 2 * len0 - 1, 1): h[2 * i] = list(h0[i]) for i in range(0, 2 * len0 - 2, 1): h[2 * i + 1] = list(h01[i]) for i in range(0, 2 * len1 - 1, 1): h[2 * i + 2] = fp.fp2_add(h[2 * i + 2], h1[i]) return h if glen == flen and (glen % 2) == 0: # Case two degree polynomials of same odd degree half = glen // 2 h0 = poly_mul(g[:half], half, f[:half], half) h1 = poly_mul(g[:half], half, f[half:], half) h = [[0, 0]] * (2 * glen - 1) for i in range(0, glen - 1, 1): h[i] = fp.fp2_add(h[i], h0[i]) for i in range(0, glen - 1, 1): h[2 * glen - 2 - i] = fp.fp2_add(h[2 * glen - 2 - i], h0[i]) for i in range(0, glen - 1, 1): h[half + i] = fp.fp2_add(h[half + i], h1[i]) for i in range(0, glen - 1, 1): h[glen + half - 2 - i] = fp.fp2_add(h[glen + half - 2 - i], h1[i]) return h # General case hlen = glen + flen - 1 m = (hlen + 1) // 2 h = poly_mul_modxn(m, g, glen, f, flen) h = h + [[0, 0]] * (hlen - m) for i in range(m, hlen, 1): h[i] = list(h[hlen - 1 - i]) return h # Product tree of given list of polynomials def product_tree(f, n): if n == 0: return {'left': None, 'right': None, 'poly': [[1, 0]], 'deg': 0} if n == 1: # No multiplication is required return { 'left': None, 'right': None, 'poly': list(f[0]), 'deg': len(f[0]) - 1, } else: m = n - (n // 2) left = product_tree(f[:m], m) right = product_tree(f[m:], n - m) return { 'left': left, 'right': right, 'poly': poly_mul(left['poly'], left['deg'] + 1, right['poly'], right['deg'] + 1), 'deg': left['deg'] + right['deg'], } # Product tree of given list of self reciprocal polynomials def product_selfreciprocal_tree(f, n): if n == 0: return {'left': None, 'right': None, 'poly': [[1, 0]], 'deg': 0} if n == 1: # No multiplication is required return { 'left': None, 'right': None, 'poly': list(f[0]), 'deg': len(f[0]) - 1, } else: m = n - (n // 2) left = product_selfreciprocal_tree(f[:m], m) right = product_selfreciprocal_tree(f[m:], n - m) return { 'left': left, 'right': right, 'poly': poly_mul_selfreciprocal(left['poly'], left['deg'] + 1, right['poly'], right['deg'] + 1), 'deg': left['deg'] + right['deg'], } # Now, the resultant of two polinomials corresponds with product of # all g(x) mod f_i(x) when each f_i is a linear polynomial def product(list_g_mod_f, n): # When finishing tests, remove the below asserts if n == 0: return [1, 0] assert len(list_g_mod_f) == n out = list(list_g_mod_f[0][0]) for j in range(1, n, 1): out = fp.fp2_mul(out, list_g_mod_f[j][0]) return out return attrdict(locals())
def print_strategy(ctx): """ draw graphs """ algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] if setting.algorithm == 'csidh': n = algo.params.n m = algo.params.m L = algo.params.L rounds = algo.gae.rounds geometric_serie = algo.gae.geometric_serie BATCHES = lambda n, sigma: [ len([j for j in range(i, n, sigma)]) for i in range(sigma) ] if len(set(m)) > 1: # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i) LABEL_m = 'different_bounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) LABEL_m = 'with_same_bounds' if setting.verbose: verb = '-suitable' else: verb = '-classical' # List of Small Odd Primes, L := [l_0, ..., l_{n-1}] m_prime = [geometric_serie(m[k], L[k]) for k in range(n)] r_out, L_out, R_out = rounds(m_prime[::-1], n) for j in range(0, len(r_out), 1): R_out[j] = list([L[::-1][k] for k in R_out[j]]) L_out[j] = list([L[::-1][k] for k in L_out[j]]) f = open(strategy_data + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) print("// Strategies to be read from a file") S_out = [] for i in range(0, len(r_out), 1): tmp = f.readline() tmp = [int(b) for b in tmp.split()] S_out.append(tmp) f.close() filename = (setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) # ---- for idx in range(0, len(S_out), 1): S = S_out[idx] n = len(S) + 1 # Strategy written as a graph vertexes, vertex_colors, edges, edge_colors = strategy_evaluation( S, n) # Simba method written as a graph # vertexes, vertex_colors, edges, edge_colors = simba(n, 3) # All the Discrete Right Triangle # vertexes, vertex_colors, edges, edge_colors = DRT(n) G = nx.Graph() # Adding nodes in specific positions G.add_nodes_from(list(range(len(vertexes)))) nx.set_node_attributes(G, vertexes, 'pos') # Adding edges with specific colors for i in range(len(edges)): G.add_edge(edges[i][0], edges[i][1], color=edge_colors[i]) # Setting variables for a pretty plot of the graph edges = G.edges() edge_colors = [G[u][v]['color'] for u, v in edges] weights = [6 for u, v in edges] vertex_sizes = [24] * len(vertexes) # Finally, the graph will be plotted plt.figure(1, figsize=(17, 17)) nx.draw( G, vertexes, node_color=['black'] * len(vertexes), node_size=vertex_sizes, edge_color=edge_colors, width=weights, edge_labels=True, ) # Saving the graph as a .PNG figure plt.savefig(filename + '-id' + str(idx) + '.png') print("saving graph: " + filename + '-id' + str(idx) + '.png') # plt.show() plt.close() print( "// The strategies have been plotted and stored in the current directory" ) else: print("bsidh not yet implemented") click.Exit(1) def DRT(n): vertexes = dict() # list of the position of each node vertex_colors = ( [] ) # color of each node: red for the leaves, otherwise color is set to white acc = 0 # Different shape of the isogeny graph for i in range(n): for j in range(n - 1 - i): vertex_colors.append('black') vertexes[acc] = (i, -j) acc += 1 return vertexes, vertex_colors, [], [] def simba(n, sigma): vertexes = dict() # list of the position of each node edges = [] # edges of the isogeny triangle edge_colors = ( [] ) # color of each edge: blue for scalar multiplications, and orange for isogeny evalutions vertex_colors = ( [] ) # color of each node: red for the leaves, otherwise color is set to white batches = BATCHES(n, sigma) t = -1 acc = 0 for i in range(sigma): t += 1 vertexes[t] = (acc, 0) # Initial vertex of the current batch vertex_colors.append('black') t += 1 vertexes[t] = (acc, -n + batches[i]) # Root of the current batch vertex_colors.append('black') edges.append((t - 1, t)) # New edge to be added edge_colors.append( 'tab:blue') # Color of this type of edge is always blue vertex = vertexes[t] for j in range(batches[i] - 1): t += 1 vertexes[t] = ( vertexes[t - 1][0], vertexes[t - 1][1] - batches[i] + j + 1, ) # Next leaf vertex_colors.append('black') edges.append((t - 1, t)) # New edge to be added edge_colors.append( 'tab:blue') # Color of this type of edge is always blue t += 1 vertexes[t] = (vertexes[t - 2][0] + 1, vertexes[t - 2][1]) vertex_colors.append('black') edges.append((t - 2, t)) # New edge to be added edge_colors.append( 'tab:red') # Color of this type of edge is always blue acc += batches[i] return vertexes, vertex_colors, edges, edge_colors return attrdict(name='print-strategy', **locals())
def MontgomeryCurve(prime, style): if True: # algorithm == 'csidh': # this is montgomery.py currently #A = parameters['csidh']['A'] <--- Not required L = parameters['csidh'][prime]['L'] n = parameters['csidh'][prime]['n'] exponent_of_two = parameters['csidh'][prime]['exponent_of_two'] p = (2**(exponent_of_two)) * reduce( lambda x, y: (x * y), L) - 1 # p := 4 * l_0 * ... * l_n - 1 # p_minus_one_halves = (p - 1) // 2 # (p - 1) / 2 p_minus_one_halves = parameters['csidh'][prime]['p_minus_one_halves'] validation_stop = sum([bitlength(l_i) for l_i in L]) / 2.0 + 2 FiniteFieldClass = PrimeField else: assert False, "bsidh not refactored yet" # this is for a possible future where we have a unified montgomery.py # for csidh and bsidh. the code in this else block was moved from fp.py f = open(sop_data + prime) # The prime to be used p = f.readline() self.p = int(p, 16) # List corresponding (p + 1) Lp = f.readline() Lp = [int(lp) for lp in Lp.split()] # exponent_of_twop = Lp[0] # Lp = Lp[1:] Ep = f.readline() Ep = [int(ep) for ep in Ep.split()] assert len(Ep) == len(Lp) np = len(Lp) # List corresponding (p - 1) Lm = f.readline() Lm = [int(lm) for lm in Lm.split()] Em = f.readline() Em = [int(em) for em in Em.split()] assert len(Em) == len(Lm) nm = len(Lm) f.close() # pp = (2**exponent_of_twop) * reduce(lambda x,y : (x*y), [ Lp[i]**Ep[i] for i in range(0, np, 1) ]) pp = reduce(lambda x, y: (x * y), [Lp[i]**Ep[i] for i in range(0, np, 1)]) pm = reduce(lambda x, y: (x * y), [Lm[i]**Em[i] for i in range(0, nm, 1)]) assert (p + 1) % pp == 0 assert (p - 1) % pm == 0 if p % 4 == 1: print("// Case p = 1 mod 4 is not implemented yet!") exit(-1) p_minus_one_halves = (p - 1) // 2 p_minus_3_quarters = (p - 3) // 4 #FiniteFieldClass = QuadraticField ff = FiniteFieldClass(p) # print("// Shortest Differential Addition Chains (SDAC) for each l_i;") # List of Small odd primes, L := [l_0, ..., l_{n-1}] # print("// SDAC's to be read from a file") path = sdacs_data + prime SDACS = filename_to_list_of_lists_of_ints(path) if len(SDACS) == 0: print("// SDAC's to be computed") SDACS = generate_sdacs(L) print("// Storing SDAC's in a file") write_list_of_lists_of_ints_to_file(path, SDACS) SDACS_LENGTH = list(map(len, SDACS)) cMUL = lambda l: numpy.array([ 4.0 * (SDACS_LENGTH[L.index(l)] + 2), 2.0 * (SDACS_LENGTH[L.index(l)] + 2), 6.0 * (SDACS_LENGTH[L.index(l)] + 2) - 2.0, ]) C_xmul = list(map(cMUL, L)) # list of the costs of each [l]P SQR = 1.00 ADD = 0.00 random = SystemRandom() def elligator(A): Ap = (A[0] + A[0]) Ap = (Ap - A[1]) Ap = (Ap + Ap) Cp = A[1] u = ff(random.randint(2, p_minus_one_halves)) u_squared = (u**2) u_squared_plus_one = (u_squared + 1) u_squared_minus_one = (u_squared - 1) C_times_u_squared_minus_one = (Cp * u_squared_minus_one) AC_times_u_squared_minus_one = (Ap * C_times_u_squared_minus_one) tmp = (Ap**2) tmp = (tmp * u_squared) aux = (C_times_u_squared_minus_one**2) tmp = (tmp + aux) tmp = (AC_times_u_squared_minus_one * tmp) alpha, beta = 0, u alpha, beta = cswap(alpha, beta, tmp == 0) u_squared_plus_one = (alpha * u_squared_plus_one) alpha = (alpha * C_times_u_squared_minus_one) Tp_X = (Ap + alpha) Tm_X = (Ap * u_squared) Tm_X = (Tm_X + alpha) Tm_X = (-Tm_X) tmp = (tmp + u_squared_plus_one) Tp_X, Tm_X = cswap(Tp_X, Tm_X, not tmp.issquare()) return ( [Tp_X, C_times_u_squared_minus_one], [Tm_X, C_times_u_squared_minus_one], ) def generate_sdacs(L): return list( map(sdac, L) ) # Shortest Differential Addition Chains for each small odd prime l in L def measure(x): """ SQR = 1.00 # In F_p, we have SQR_{F_p} = SQR x MUL_{F_p} ADD = 0.00 # In F_p, we have ADD_{F_p} = ADD x MUL_{F_p} """ return x[0] + SQR * x[1] + ADD * x[2] def dacs(l, r0, r1, r2, chain): ''' dacs() inputs: a small odd prime number l, three integer numbers, and a list output: all the differential additions chains corresponding with the input l NOTE: this is a recursive approach ''' if r2 == l: return [(chain, r2)] elif r2 < l and len(chain) <= 1.5 * math.log(l, 2): return dacs(l, r0, r2, r2 + r0, chain + [1]) + dacs( l, r1, r2, r2 + r1, chain + [0]) else: return [] def sdac(l): ''' sdac() input: a small odd prime number l output: the shortest differential additions chains corresponding with the input l NOTE: this function uses a recursive function ''' all_dacs = dacs(l, 1, 2, 3, []) return min(all_dacs, key=lambda t: len(t[0]))[0] def affine_to_projective(affine): """ input : the affine Montgomery coefficient A=A'/C with C=1 output: projective Montgomery constants A24 := A' + 2C and C24 := 4C where E : y^2 = x^3 + (A'/C)*x^2 + x """ return [affine + ff(2), ff(4)] def coeff(A): ''' ---------------------------------------------------------------------- coeff() input : projective Montgomery constants A24 := A + 2C and C24 := 4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the affine Montgomery coefficient A/C ---------------------------------------------------------------------- ''' output = (A[0] + A[0]) # (2 * A24) output = (output - A[1]) # (2 * A24) - C24 C24_inv = (A[1]**-1) # 1 / (C24) output = (output + output) # 4*A = 2[(2 * A24) - C24] output = (output * C24_inv) # A/C = 2[(2 * A24) - C24] / C24 return output def isinfinity(P): """ isinfinity(P) determines if x(P) := (XP : ZP) = (1 : 0) """ return P[1] == 0 def isequal(P, Q): """ isequal(P, Q) determines if x(P) = x(Q) """ return (P[0] * Q[1]) == (P[1] * Q[0]) def xdbl(P, A): ''' ---------------------------------------------------------------------- xdbl() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, and the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the projective Montgomery x-coordinate point x([2]P) ---------------------------------------------------------------------- ''' t_0 = (P[0] - P[1]) t_1 = (P[0] + P[1]) t_0 = (t_0**2) t_1 = (t_1**2) Z = (A[1] * t_0) X = (Z * t_1) t_1 = (t_1 - t_0) t_0 = (A[0] * t_1) Z = (Z + t_0) Z = (Z * t_1) return [X, Z] def xadd(P, Q, PQ): ''' ---------------------------------------------------------------------- xadd() input : the projective Montgomery x-coordinate points x(P) := XP/ZP, x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ output: the projective Montgomery x-coordinate point x(P+Q) ---------------------------------------------------------------------- ''' a = (P[0] + P[1]) b = (P[0] - P[1]) c = (Q[0] + Q[1]) d = (Q[0] - Q[1]) a = (a * d) b = (b * c) c = (a + b) d = (a - b) c = (c**2) d = (d**2) X = (PQ[1] * c) Z = (PQ[0] * d) return [X, Z] # Modificar esta parte para usar cadenas de addicion def xmul(P, A, j): ''' ---------------------------------------------------------------------- xmul() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and an positive integer j output: the projective Montgomery x-coordinate point x([L[j]]P) ---------------------------------------------------------------------- ''' P2 = xdbl(P, A) R = [P, P2, xadd(P2, P, P)] for i in range(SDACS_LENGTH[j] - 1, -1, -1): if isinfinity(R[SDACS[j][i]]): T = xdbl(R[2], A) else: T = xadd(R[2], R[SDACS[j][i] ^ 1], R[SDACS[j][i]]) R[0] = list(R[SDACS[j][i] ^ 1]) R[1] = list(R[2]) R[2] = list(T) return R[2] def cofactor_multiples(P, A, points): ''' ---------------------------------------------------------------------- cofactor_multiples() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and subset of |[0, n]| output: the projective Montgomery x-coordinate points x([(p+1) / l_0]P), x([(p+1) / l_1]P), ..., x([(p+1) / l_{n-1}]P). ---------------------------------------------------------------------- ''' n = len(points) if n == 1: # In this recursion level we have an order-l point return [P] elif n > 0: # We proceed by applying a divide-and-conquer procedure h = n // 2 if h > 0: # 1st half first_half = [] second_P = P for j in range(h): second_P = xmul(second_P, A, points[j]) first_half.append(points[j]) # 2nd half second_half = [] first_P = P for j in range(h, n): first_P = xmul(first_P, A, points[j]) second_half.append(points[j]) return cofactor_multiples(first_P, A, first_half) + cofactor_multiples( second_P, A, second_half) return [] def isfullorder(seq): tmp = [not isinfinity(seq_i) for seq_i in seq] return reduce(lambda x, y: (x and y), tmp) def generators(A): output = [[0, 0], [0, 0]] if style != 'wd1' else [[0, 0], [1, 1]] while [0, 0] in output: T_p, T_m = elligator(A) for i in range(0, exponent_of_two, 1): T_p = xdbl(T_p, A) if isfull_order(cofactor_multiples(T_p, A, range( 0, n, 1))) and output[0] == [0, 0]: output[0] = list(T_p) if style != 'wd1': for i in range(0, exponent_of_two, 1): T_m = xdbl(T_m, A) if isfull_order(cofactor_multiples(T_m, A, range( 0, n, 1))) and output[1] == [0, 0]: output[1] = list(T_m) return output[0], output[1] def crisscross(alpha, beta, gamma, delta): t_1 = (alpha * delta) t_2 = (beta * gamma) return (t_1 + t_2), (t_1 - t_2) def issupersingular(A): while True: T_p, _ = elligator(A) for i in range(0, exponent_of_two, 1): T_p = xdbl(T_p, A) P = cofactor_multiples(T_p, A, range(0, n, 1)) bits_of_the_order = 0 for i in range(0, n, 1): if isinfinity(P[i]) == False: Q = xmul(P[i], A, i) if isinfinity(Q) == False: return False bits_of_the_order += bitlength(L[i]) if bits_of_the_order > validation_stop: return True return attrdict(**locals())
def Tvelu(curve): fp = curve.fp global_L = curve.L cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)]) cISOG = lambda l: numpy.array([ (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0), (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0), (3.0 * l - 7.0 + isequal[l == 3] * 6.0), ]) C_xEVAL = list( map(cEVAL, global_L)) # list of the costs of each degree-l isogeny evaluation C_xISOG = list(map( cISOG, global_L)) # list of the costs of each degree-l isogeny construction K = None ''' ------------------------------------------------------------------------- yDBL() input : a projective Twisted Edwards y-coordinate point y(P) := YP/WP, and the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the projective Twisted Edwards y-coordinate point y([2]P) ------------------------------------------------------------------------- ''' def yDBL(P, A): t_0 = fp.fp_sqr(P[0]) t_1 = fp.fp_sqr(P[1]) Z = fp.fp_mul(A[1], t_0) X = fp.fp_mul(Z, t_1) t_1 = fp.fp_sub(t_1, t_0) t_0 = fp.fp_mul(A[0], t_1) Z = fp.fp_add(Z, t_0) Z = fp.fp_mul(Z, t_1) return [fp.fp_sub(X, Z), fp.fp_add(X, Z)] ''' ------------------------------------------------------------------------- yADD() input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP, y(Q) := YQ/WQ, and y(P-Q) := YPQ/QPQ output: the projective Twisted Edwards y-coordinate point y(P+Q) ------------------------------------------------------------------------- ''' def yADD(P, Q, PQ): a = fp.fp_mul(P[1], Q[0]) b = fp.fp_mul(P[0], Q[1]) c = fp.fp_add(a, b) d = fp.fp_sub(a, b) c = fp.fp_sqr(c) d = fp.fp_sqr(d) xD = fp.fp_add(PQ[1], PQ[0]) zD = fp.fp_sub(PQ[1], PQ[0]) X = fp.fp_mul(zD, c) Z = fp.fp_mul(xD, d) return [fp.fp_sub(X, Z), fp.fp_add(X, Z)] ''' ------------------------------------------------------------------------- KPs() input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and a positive integer 0 <= i < n output: the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1 ------------------------------------------------------------------------- ''' def KPs(P, A, i): nonlocal K d = (global_L[i] - 1) // 2 K = [[0, 0] for j in range(d + 1)] K[0] = list([fp.fp_sub(P[0], P[1]), fp.fp_add(P[0], P[1])]) # 2a K[1] = yDBL(K[0], A) # 4M + 2S + 4a for j in range(2, d, 1): K[j] = yADD(K[j - 1], K[0], K[j - 2]) # 4M + 2S + 6a return K # 2(l - 3)M + (l - 3)S + 3(l - 3)a ''' ------------------------------------------------------------------------------ xISOG() input : the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n output: the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E ------------------------------------------------------------------------------ ''' def xISOG(A, i): nonlocal K l = global_L[i] # l bits_of_l = bitlength(l) # Number of bits of L[i] d = (l - 1) // 2 # Here, l = 2d + 1 By = K[0][0] Bz = K[0][1] for j in range(1, d, 1): By = fp.fp_mul(By, K[j][0]) Bz = fp.fp_mul(Bz, K[j][1]) bits_of_l -= 1 constant_d_edwards = fp.fp_sub(A[0], A[1]) # 1a tmp_a = A[0] tmp_d = constant_d_edwards # left-to-right method for computing a^l and d^l for j in range(1, bits_of_l + 1): tmp_a = fp.fp_sqr(tmp_a) tmp_d = fp.fp_sqr(tmp_d) if ((l >> (bits_of_l - j)) & 1) != 0: tmp_a = fp.fp_mul(tmp_a, A[0]) tmp_d = fp.fp_mul(tmp_d, constant_d_edwards) for j in range(3): By = fp.fp_sqr(By) Bz = fp.fp_sqr(Bz) C0 = fp.fp_mul(tmp_a, Bz) C1 = fp.fp_mul(tmp_d, By) C1 = fp.fp_sub(C0, C1) return [C0, C1] # (l - 1 + 2*HW(l) - 2)M + 2(|l|_2 + 1)S + 2a ''' ------------------------------------------------------------------------------ xEVAL() input : the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n output: the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E ------------------------------------------------------------------------------ ''' def xEVAL(P, i): nonlocal K d = (global_L[i] - 1) // 2 # Here, l = 2d + 1 Q0 = fp.fp_add(P[0], P[1]) Q1 = fp.fp_sub(P[0], P[1]) R0, R1 = curve.CrissCross(K[0][1], K[0][0], Q0, Q1) for j in range(1, d, 1): T0, T1 = curve.CrissCross(K[j][1], K[j][0], Q0, Q1) R0 = fp.fp_mul(T0, R0) R1 = fp.fp_mul(T1, R1) R0 = fp.fp_sqr(R0) R1 = fp.fp_sqr(R1) X = fp.fp_mul(P[0], R0) Z = fp.fp_mul(P[1], R1) return [X, Z] # 2(l - 1)M + 2S + (l + 1)a return attrdict(name='tvelu', **locals())
def bsidh_main(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] curve = algo.curve tuned_name = ('-classical', '-suitable')[setting.tuned] SIDp = algo.curve.SIDp SIDm = algo.curve.SIDm SQR, ADD = algo.curve.SQR, algo.curve.ADD p = algo.params.p global_L = algo.curve.L coeff = curve.coeff random = SystemRandom() fp = curve.fp f_name = 'data/strategies/' + setting.algorithm + '-' + setting.prime + '-' + algo.formula.name + tuned_name try: f = open(resource_filename('sidh', f_name)) print("// Strategies to be read from a file") # Corresponding to the list of Small Isogeny Degree, Lp := [l_0, ..., # l_{n-1}] [We need to include case l=2 and l=4] tmp = f.readline() tmp = [int(b) for b in tmp.split()] Sp = list(tmp) # Corresponding to the list of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}] tmp = f.readline() tmp = [int(b) for b in tmp.split()] Sm = list(tmp) f.close() except IOError: print("// Strategies to be computed") # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to # include case l=2 and l=4] Sp, Cp = dynamic_programming_algorithm(SIDp[::-1], len(SIDp)) # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}] Sm, Cm = dynamic_programming_algorithm(SIDm[::-1], len(SIDm)) f = open(f_name, 'w') f.writelines(' '.join([str(tmp) for tmp in Sp]) + '\n') f.writelines(' '.join([str(tmp) for tmp in Sm]) + '\n') f.close() print( "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The curve.measures are given in millions of field operations.\n" % (SQR, ADD)) print("p := 0x%X;" % p) print("fp := GF(p);") print("_<x> := PolynomialRing(fp);") print("fp2<i> := ext<fp | x^2 + 1>;") print("Pr<x> := PolynomialRing(fp2);") # Reading public generators points f = open(resource_filename('sidh', 'data/gen/' + setting.prime)) # x(PA), x(QA) and x(PA - QA) PQA = f.readline() PQA = [int(x, 16) for x in PQA.split()] PA = [list(PQA[0:2]), [0x1, 0x0]] QA = [list(PQA[2:4]), [0x1, 0x0]] PQA = [list(PQA[4:6]), [0x1, 0x0]] # x(PB), x(QB) and x(PB - QB) PQB = f.readline() PQB = [int(x, 16) for x in PQB.split()] PB = [list(PQB[0:2]), [0x1, 0x0]] QB = [list(PQB[2:4]), [0x1, 0x0]] PQB = [list(PQB[4:6]), [0x1, 0x0]] f.close() A = [[0x8, 0x0], [0x4, 0x0]] a = coeff(A) print("public_coeff := 0x%X + i * 0x%X;\n" % (a[0], a[1])) print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) print("// Public Key Generation") print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n" ) # Alice's side print("// Private key corresponding to Alice") a_private = algo.gae.random_key(p + 1) fp.fp.set_zero_ops() Ra = curve.Ladder3pt(a_private, PA, QA, PQA, A) print("// sk_a := 0x%X;" % a_private) RUNNING_TIME = fp.fp.get_ops() print( "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;\n" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) print("// Public key corresponding to Alice") fp.fp.set_zero_ops() a_public, PB_a, QB_a, PQB_a = algo.gae.evaluate_strategy( True, PB, QB, PQB, A, Ra, SIDp[::-1], Sp, len(SIDp)) RUNNING_TIME = fp.fp.get_ops() print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) a_curve = coeff(a_public) print("pk_a := 0x%X + i * 0x%X;\n" % (a_curve[0], a_curve[1])) # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (a_curve[0], a_curve[1]) ) # print("assert(Random(B) * (p + 1) eq B!0);") print("// Private key corresponding to Bob") b_private = algo.gae.random_key(p - 1) print("// sk_b := 0x%X;" % b_private) fp.fp.set_zero_ops() Rb = curve.Ladder3pt(b_private, PB, QB, PQB, A) RUNNING_TIME = fp.fp.get_ops() print( "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;\n" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) print("// Public key corresponding to Bob") fp.fp.set_zero_ops() b_public, PA_b, QA_b, PQA_b = algo.gae.evaluate_strategy( True, PA, QA, PQA, A, Rb, SIDm[::-1], Sm, len(SIDm)) RUNNING_TIME = fp.fp.get_ops() print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) b_curve = coeff(b_public) print("pk_b := 0x%X + i * 0x%X;\n" % (b_curve[0], b_curve[1])) # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (b_curve[0], b_curve[1]) ) # print("assert(Random(B) * (p + 1) eq B!0);") # ====================================================== print( "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) print("// Secret Sharing Computation") print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n" ) print("// Secret sharing corresponding to Alice") fp.fp.set_zero_ops() RB_a = curve.Ladder3pt(a_private, PA_b, QA_b, PQA_b, b_public) RUNNING_TIME = fp.fp.get_ops() print( "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) fp.fp.set_zero_ops() ss_a, _, _, _ = algo.gae.evaluate_strategy(False, PB, QB, PQB, b_public, RB_a, SIDp[::-1], Sp, len(SIDp)) RUNNING_TIME = fp.fp.get_ops() print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) ss_a_curve = coeff(ss_a) print("ss_a := 0x%X + i * 0x%X;\n" % (ss_a_curve[0], ss_a_curve[1])) # ss_ja = jinvariant(ss_a) # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (ss_a_curve[0], ss_a_curve[1]) ) # print("jB := 0x%X + i * 0x%X;" % (ss_ja[0], ss_ja[1]) ) # print("assert(Random(B) * (p + 1) eq B!0);") # print("assert(jInvariant(B) eq jB);") print("// Secret sharing corresponding to Bob") fp.fp.set_zero_ops() RA_b = curve.Ladder3pt(b_private, PB_a, QB_a, PQB_a, a_public) RUNNING_TIME = fp.fp.get_ops() print( "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) fp.fp.set_zero_ops() ss_b, _, _, _ = algo.gae.evaluate_strategy(False, PA, QA, PQA, a_public, RA_b, SIDm[::-1], Sm, len(SIDm)) RUNNING_TIME = fp.fp.get_ops() print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), curve.measure(RUNNING_TIME) / (10.0**6), )) ss_b_curve = coeff(ss_b) print("ss_b := 0x%X + i * 0x%X;\n" % (ss_b_curve[0], ss_b_curve[1])) # ss_jb = jinvariant(ss_b) # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (ss_b_curve[0], ss_b_curve[1]) ) # print("jB := 0x%X + i * 0x%X;" % (ss_jb[0], ss_jb[1]) ) # print("assert(Random(B) * (p + 1) eq B!0);") # print("assert(jInvariant(B) eq jB);") print( "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) if ss_a_curve == ss_b_curve: print('\x1b[0;30;43m' + '\"Successfully passed!\";' + '\x1b[0m') else: print('\x1b[0;30;41m' + '\"Great Scott!... The sky is falling. NOT PASSED!!!\"' + '\x1b[0m') return attrdict(name='bsidh-main', **locals())
def F_p2(p): fp = F_p(p) # Addition in the quadratic extension field of fp def fp2_add(a, b): return [fp.fp_add(a[0], b[0]), fp.fp_add(a[1], b[1])] # Substraction in the quadratic extension field of fp def fp2_sub(a, b): return [fp.fp_sub(a[0], b[0]), fp.fp_sub(a[1], b[1])] # Product in the quadratic extension field of fp def fp2_mul(a, b): z0 = fp.fp_add(a[0], a[1]) z1 = fp.fp_add(b[0], b[1]) t = fp.fp_mul(z0, z1) z2 = fp.fp_mul(a[0], b[0]) z3 = fp.fp_mul(a[1], b[1]) c = [0, 0] c[0] = fp.fp_sub(z2, z3) c[1] = fp.fp_sub(t, z2) c[1] = fp.fp_sub(c[1], z3) return c # Squaring in the quadratic extension field of fp def fp2_sqr(a): z0 = fp.fp_add(a[0], a[0]) z1 = fp.fp_add(a[0], a[1]) z2 = fp.fp_sub(a[0], a[1]) b = [0, 0] b[0] = fp.fp_mul(z1, z2) b[1] = fp.fp_mul(z0, a[1]) return b # Inverse in the quadratic extension field of fp def fp2_inv(a): N0 = fp.fp_sqr(a[0]) N1 = fp.fp_sqr(a[1]) S1 = fp.fp_add(N0, N1) S1 = fp.fp_inv(S1) b = [0, 0] S2 = fp.fp_sub(0, a[1]) b[0] = fp.fp_mul(S1, a[0]) b[1] = fp.fp_mul(S1, S2) return b # Exponentiation in the quadratic extension field of fp def fp2_exp(a, e): bits_of_e = bitlength(e) bits_of_e -= 1 tmp_a = list(a) # left-to-right method for computing a^e for j in range(1, bits_of_e + 1): tmp_a = fp2_sqr(tmp_a) if ((e >> (bits_of_e - j)) & 1) != 0: tmp_a = fp2_mul(tmp_a, a) return tmp_a # Sqrt (if exists) in the quadratic extension field of fp def fp2_issquare(a): a1 = fp2_exp(a, p_minus_3_quarters) alpha = fp2_sqr(a1) alpha = fp2_mul(alpha, a) alpha_conjugated = list(alpha) alpha_conjugated[1] = fp.fp_sub(0, alpha_conjugated[1]) a0 = fp2_mul(alpha, alpha_conjugated) if a0[1] == 0 and a0[0] == (p - 1): # a doesn't have sqrt in fp2 return False, None x0 = fp2_mul(a1, a) if alpha[1] == 0 and alpha[0] == (p - 1): return True, [fp.fp_sub(0, x0[1]), x0[0]] else: alpha[0] = fp.fp_add(alpha[0], 1) b = fp2_exp(alpha, p_minus_one_halves) b = fp2_mul(b, x0) return True, b def fp2_cswap(x, y, b): z0, w0 = fp.fp_cswap(x[0], y[0], b) z1, w1 = fp.fp_cswap(x[1], y[1], b) return [z0, z1], [w0, w1] """ # Tests print("p := 0x%X;" % p) print("fp := GF(p);") print("P<x> := PolynomialRing(fp);"); print("fp2<i> := ext<fp | x^2 + 1>;") print("P<x> := PolynomialRing(fp2);"); a = [random.randint(0, p - 1), random.randint(0, p - 1)] b = [random.randint(0, p - 1), random.randint(0, p - 1)] print("a := 0x%X + 0x%X * i;" % (a[0], a[1])) print("b := 0x%X + 0x%X * i;" % (b[0], b[1])) e = random.randint(0, p - 1) print("e := 0x%X;" % e) c = fp2_add(a, b) print("(0x%X + 0x%X * i) eq (a+b);" % (c[0], c[1])) c = fp2_sub(a, b) print("(0x%X + 0x%X * i) eq (a-b);" % (c[0], c[1])) c = fp2_mul(a, b) print("(0x%X + 0x%X * i) eq (a*b);" % (c[0], c[1])) c = fp2_sqr(a) print("(0x%X + 0x%X * i) eq (a^2);" % (c[0], c[1])) c = fp2_inv(a) print("(0x%X + 0x%X * i) eq (1/a);" % (c[0], c[1])) c = fp2_exp(a, e) print("(0x%X + 0x%X * i) eq (a^e);" % (c[0], c[1])) t, c = fp2_issquare(a) if t: print("(0x%X + 0x%X * i)^2 eq a;" % (c[0], c[1])) """ return attrdict(**locals())
def csidh_main(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] full_torsion_points = algo.curve.full_torsion_points coeff = algo.curve.coeff p = algo.params.p global_L = L = algo.params.L m = algo.params.m n = algo.params.n geometric_serie = algo.gae.geometric_serie rounds = algo.gae.rounds SQR, ADD = algo.curve.SQR, algo.curve.ADD set_zero_ops = algo.fp.set_zero_ops get_ops = algo.fp.get_ops validate = algo.curve.validate measure = algo.curve.measure GAE = algo.gae.GAE strategy_block_cost = algo.gae.strategy_block_cost random_key = algo.gae.random_key if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu temporal_m = list(set(m)) if len(temporal_m) > 1: # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i) LABEL_m = 'different_bounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) LABEL_m = 'with_same_bounds' if setting.tuned: verb = '-suitable' else: verb = '-classical' # List of Small Odd Primes, L := [l_0, ..., l_{n-1}] m_prime = [geometric_serie(m[k], L[k]) for k in range(n)] r_out, L_out, R_out = rounds(m_prime[::-1], n) for j in range(0, len(r_out), 1): R_out[j] = list([L[::-1][k] for k in R_out[j]]) L_out[j] = list([L[::-1][k] for k in L_out[j]]) file_path = ("data/strategies/" + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) file_path = resource_filename('sidh', file_path) try: f = open(file_path) print("// Strategies to be read from a file") S_out = [] for i in range(0, len(r_out), 1): tmp = f.readline() tmp = [int(b) for b in tmp.split()] S_out.append(tmp) f.close() except IOError: print("// Strategies to be computed") C_out, L_out, R_out, S_out, r_out = strategy_block_cost( L[::-1], m[::-1]) f = open(file_path, 'w') for i in range(0, len(r_out)): f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n') f.close() print( "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The measures are given in millions of field operations.\n" % (SQR, ADD)) ''' ------------------------------------------------------------------------------------- Framework ------------------------------------------------------------------------------------- ''' print("p := 0x%X;" % p) print("fp := GF(p);") print("P<x> := PolynomialRing(fp);") print("fp2<i> := ext<fp | x^2 + 1>;") print("P<x> := PolynomialRing(fp2);") A = [2, 4] print("public_coeff := 0x%X;\n" % coeff(A)) ''' ------------------------------------------------------------------------------------- Main ------------------------------------------------------------------------------------- ''' print("// Maximum number of degree-(\\ell_i) isogeny constructions: m_i") print("/*") printl("m", m, n // 3) print("*/") print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) print("// Public Key Generation") print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n" ) # ------------------------------------------------------------------------- Alice set_zero_ops() public_validation = validate(A) assert public_validation a_private = random_key(m) print("// Private key corresponding to Alice") print("/*") printl("sk_a", a_private, n // 3) print("*/") RUNNING_TIME = get_ops() print("// Public key corresponding to Alice") print( "// public key validation cost :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) set_zero_ops() if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): a_public = GAE( A, a_private, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: a_public = GAE(A, a_private, L_out, R_out, S_out, r_out, m) RUNNING_TIME = get_ops() print( "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) print("pk_a := %r;\n" % list(map(hex, a_public))) print("pk_a := 0x%X;\n" % coeff(a_public)) # ------------------------------------------------------------------------- Bob set_zero_ops() b_private = random_key(m) print("// Private key corresponding to Bob") print("/*") printl("sk_b", b_private, n // 3) print("*/") set_zero_ops() public_validation = validate(A) assert public_validation RUNNING_TIME = get_ops() print("// Public key corresponding to Bob") print( "// public key validation cost :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) set_zero_ops() if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): b_public = GAE( A, b_private, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: b_public = GAE(A, b_private, L_out, R_out, S_out, r_out, m) RUNNING_TIME = get_ops() print( "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) print("pk_b := %r;\n" % list(map(hex, b_public))) print("pk_b := 0x%X;" % coeff(b_public)) print( "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) print("// Secret Sharing Computation") print( "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n" ) # ------------------------------------------------------------------------- Alice set_zero_ops() public_validation = validate(b_public) assert public_validation RUNNING_TIME = get_ops() print("// Secret sharing corresponding to Alice") print( "// public key validation cost :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) set_zero_ops() if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): ss_a = GAE( b_public, a_private, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: ss_a = GAE(b_public, a_private, L_out, R_out, S_out, r_out, m) RUNNING_TIME = get_ops() print( "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) print("ss_a := 0x%X;\n" % coeff(ss_a)) print( "expected: 0x1ADB783878BA330BB2A842E7F8B3392329A2CD3B407900E4CF6A8F13B744BFFEFF617BDE2CEBBB9CE97D32BC6FC1BCE2D88381B03B3E13CFF0651EEA82D02937" ) # ------------------------------------------------------------------------- Bob set_zero_ops() public_validation = validate(a_public) assert public_validation RUNNING_TIME = get_ops() print("// Secret sharing corresponding to Bob") print( "// public key validation cost :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) set_zero_ops() if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and (0 in temporal_m)): ss_b = GAE( a_public, b_private, [L_out[0]], [R_out[0]], [S_out[0]], [temporal_m[-1]], m, ) else: ss_b = GAE(a_public, b_private, L_out, R_out, S_out, r_out, m) RUNNING_TIME = get_ops() print( "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) print("ss_b := 0x%X;\n" % coeff(ss_b)) print( "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ) if coeff(ss_a) == coeff(ss_b): print('\x1b[0;30;43m' + '\"Successfully passed!\";' + '\x1b[0m') else: print('\x1b[0;30;41m' + '\"Great Scott!... The sky is falling. NOT PASSED!!!\"' + '\x1b[0m') return attrdict(name='csidh-main', **locals())
def csidh_strategy(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m delta = algo.params.delta geometric_serie = algo.gae.geometric_serie strategy_block_cost = algo.gae.strategy_block_cost rounds = algo.gae.rounds sigma, kappa = algo.params.sigma, algo.params.kappa # ========================================================================== if len(set(m)) > 1: # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i) LABEL_m = 'different_bounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) LABEL_m = 'with_same_bounds' if setting.tuned: verb = '-suitable' else: verb = '-classical' try: # List of Small Odd Primes, L := [l_0, ..., l_{n-1}] m_prime = [geometric_serie(m[k], L[k]) for k in range(n)] r_out, L_out, R_out = rounds(m_prime[::-1], n) for j in range(0, len(r_out), 1): R_out[j] = list([L[::-1][k] for k in R_out[j]]) L_out[j] = list([L[::-1][k] for k in L_out[j]]) f = open(strategy_data + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) print("// Strategies to be read from a file") S_out = [] for i in range(0, len(r_out), 1): tmp = f.readline() tmp = [int(b) for b in tmp.split()] S_out.append(tmp) f.close() except IOError: print("// Strategies to be computed") C_out, L_out, R_out, S_out, r_out = strategy_block_cost( L[::-1], m[::-1]) f = open( strategy_data + setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb, 'w', ) for i in range(0, len(r_out)): f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n') f.close() filename = (setting.algorithm + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + LABEL_m + verb) return attrdict(name='csidh-strategy', **locals())
class known_df_p512(object): """ Known-answer tests We currently only have these for p512 df keys. These keys and answers came from running csidh/main.py (which generated these keys) prior to the big refactoring. To run this test for just one formula, you can run commands like this: pytest -k 'known_df and hvelu' """ keys = attrdict( A=attrdict( sk=[ # fmt: off 9, 2, -14, 3, 11, -12, 4, 4, 0, 20, 8, 7, -12, -20, 23, 15, 5, 3, 15, -19, 7, -17, -19, 1, -2, 14, 0, -9, 2, 4, 11, 2, 7, 9, 9, -1, 5, -7, 5, 4, -4, 6, 6, 7, -8, -2, 0, 2, -6, 5, -2, 0, -2, 4, -5, -1, -5, 3, 3, 5, 3, -5, 5, 3, -2, 2, -4, -2, 0, -2, 2, 0, 2, -3, # fmt: on ], pk=[ 0x2B84079DFE34DACA1000B23DE50ED8E581C2F8F174FDBCB03D3CA6C96361E731152F45BDD4959832DE406E8F3C4F0B4949C4826AF82B3A7362677A458196FBCF, 0x76599305D04FB32F8907B2279EE35BE99786DA7C055E4A922712A6B546D457FA8DB9529049BBE500BE47E23DAE04ECD34E043264A02BB1917DFDF9FA540F233, ], compressed=0x27AD85DDC08BF510F08A8562BA4909803675536A0BCE6250E3BED4A9401AC123FE75C18866625E9FCFCAF03D0927ED46665E153E786244DAAAC9A83075060C82, ), B=attrdict( sk=[ # fmt: off 3, -16, -10, 1, 15, 20, -20, -22, -16, -22, 0, -19, 6, -4, -9, 13, -11, 13, -13, -1, 23, 21, -5, 13, -4, -2, 12, 15, -4, -10, -5, 0, 11, 1, -1, -1, 7, 1, -3, 6, 0, 2, -4, -5, 0, 2, -4, -2, -4, -5, 6, 2, -6, -4, 5, -5, 5, -3, 1, 3, -1, -5, 3, -5, -4, 2, 4, 2, 2, 4, 0, -2, 0, -3, # fmt: on ], pk=[ 0x5E2FD48334E49B47BB754F88B3345C77D604EB1FADC29B35B931724459143ABDE2F22346B595E3B161D80F3659870F16D4983BFA58F5F2F9718D3B375C21D65C, 0x314B346A927C21051052B790809D895627ED8FBE4F008408D361223A97556EC0E6D3B544B0898DAFFCDBFF5C5B409CCB5CC9E2EDC95504FCA54318071E28E054, ], compressed=0x181C39753CCB4D3358E32B4471EE73EDC568846CA3B0B17571A09BD7373B4658251ADF466FF1FFB29D89B382184703C708F71497611A4B643BD984D847F3A430, ), ) ss = 0x1ADB783878BA330BB2A842E7F8B3392329A2CD3B407900E4CF6A8F13B744BFFEFF617BDE2CEBBB9CE97D32BC6FC1BCE2D88381B03B3E13CFF0651EEA82D02937 prime = 'p512' formula = NotImplemented style = 'df' tuned = False exponent = 2 multievaluation = False verbose = False def setUp(self): self.csidh = CSIDH( 'montgomery', self.prime, self.formula, self.style, self.tuned, self.exponent, self.multievaluation, self.verbose, ) self.coeff = self.csidh.curve.coeff def test_dh_AB(self): self.assertEqual( self.coeff(self.csidh.gae.dh(self.keys.A.sk, self.keys.B.pk)), self.ss, ) def test_dh_BA(self): self.assertEqual( self.coeff(self.csidh.gae.dh(self.keys.B.sk, self.keys.A.pk)), self.ss, ) def test_compress(self): for keys in self.keys.values(): self.assertEqual(keys.compressed, self.csidh.curve.coeff(keys.pk)) def test_compress_roundtrip(self): compress, uncompress = ( self.csidh.curve.coeff, self.csidh.curve.affine_to_projective, ) for keys in self.keys.values(): self.assertEqual( keys.compressed, compress(uncompress(compress(keys.pk))) )
def Gae(prime, tuned, curve, formula): ''' >>> from sidh.bsidh.strategy import Gae >>> from sidh.bsidh.montgomery import MontgomeryCurve >>> from sidh.bsidh.hvelu import Hvelu >>> import pdb >>> prime = 'b2' >>> tuned = False >>> multievaluation = False >>> curve = MontgomeryCurve(prime) >>> formula = Hvelu(curve, tuned, multievaluation) >>> gae = Gae(prime, tuned, curve, formula) >>> sk_a = 0x642DCCC20D71FAFDFBA18D94E19777E8601494E718CB04E330C4BFE0181C209 >>> sk_b = 0x135EB57C05FD58E80531C7CDDE36F2A7BBA88C55E8A70A0A97917D554AFA1EB6 >>> pk_a = gae.pubkey_A(sk_a) >>> a_curve = curve.coeff(pk_a) >>> pk_b = gae.pubkey_B(sk_b) >>> b_curve = curve.coeff(pk_b) >>> ss_a = gae.dh_A(sk_a, pk_b) >>> curve_ss_a = curve.coeff(ss_a) >>> ss_b = gae.dh_B(sk_b, pk_a) >>> curve_ss_b = curve.coeff(ss_b) >>> assert curve_ss_a == curve_ss_b ''' fp = curve.fp global_L = curve.L n = curve.n m = curve.p curve = curve random = SystemRandom() tuned_name = ('-classical', '-suitable')[tuned] SQR, ADD = curve.SQR, curve.ADD SIDp = curve.SIDp SIDm = curve.SIDm # Reading public generators points f = open(resource_filename('sidh', 'data/gen/' + prime)) # x(PA), x(QA) and x(PA - QA) PQA = f.readline() PQA = [int(x, 16) for x in PQA.split()] PA = [list(PQA[0:2]), [0x1, 0x0]] QA = [list(PQA[2:4]), [0x1, 0x0]] PQA = [list(PQA[4:6]), [0x1, 0x0]] # x(PB), x(QB) and x(PB - QB) PQB = f.readline() PQB = [int(x, 16) for x in PQB.split()] PB = [list(PQB[0:2]), [0x1, 0x0]] QB = [list(PQB[2:4]), [0x1, 0x0]] PQB = [list(PQB[4:6]), [0x1, 0x0]] f.close() # These are for nonlocal in the pk/dh functions PA_b, QA_b, PQA_b = None, None, None PB_a, QB_a, PQB_a = None, None, None A = [[0x8, 0x0], [0x4, 0x0]] a = curve.coeff(A) # random_key() implements an uniform random integer sample [this functions should be modified] random = SystemRandom() random_key_A = lambda m=m: random.randint(0, m + 1) random_key_B = lambda m=m: random.randint(0, m - 1) random_key = lambda m=m: random.randint(0, m) # In order to achieve efficiency, the optimal strategies and their cost are saved in two global dictionaries (hash tables) S = {1: {}} # Initialization of each strategy C = {1: {}} # Initialization of the costs: 0. for i in range(n): j = global_L.index(curve.SID[i]) S[1][tuple([global_L[j]])] = [] # Strategy with a list with only one element (a small odd prime number l_i) C[1][tuple([ global_L[j] ])] = formula.C_xISOG[j] # Degree-l_i isogeny construction cost for i in range(2, n + 1): C[i] = {} S[i] = {} def dynamic_programming_algorithm(L, n): ''' dynamic_programming_algorithm(): inputs: the list of small odd primes to be processed and its length output: the optimal strategy and its cost of the input list of small odd primes ''' nonlocal S, C # If the approach uses dummy operations, to set DUMMY = 2.0; # otherwise, to set DUMMY = 1.0 (dummy free approach); if len(L) != n: # If the list of prime numbers doesn't have size n, then we return [],-1 print( "error:\tthe list of prime numbers has different size from %d." % n) return [], -1 else: # Assuming #L = n, we proceed. get_neighboring_sets = lambda L, k: [ tuple(L[i:i + k]) for i in range(n - k + 1) ] # This function computes all the k-tuple: (l_1, l_2, ..., l_{k)), # (l_2, l_3, ..., l_{k+1)), ..., (l_{n-k}, l_{n-k+1, ..., l_{n)). for i in range(2, n + 1): for Tuple in get_neighboring_sets(L, i): if C[i].get(Tuple) is None: alpha = [ ( b, C[len(Tuple[:b])][Tuple[:b]] + C[ # Subtriangle on the left side with b leaves len(Tuple[b:])][Tuple[b:]] + 1.0 # Subtriangle on the right side with (i - b) leaves * sum([ curve.C_xMUL[global_L.index(t)] for t in Tuple[:b] ]) + 1.0 # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with b leaves * sum([ formula.C_xEVAL[global_L.index(t)] for t in Tuple[b:] ]), # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves ) for b in range(1, i) ] b, C[i][Tuple] = min( alpha, key=lambda t: curve.measure(t[1]) ) # We save the minimal cost corresponding to the triangle with leaves Tuple S[i][Tuple] = ( [b] + S[i - b][Tuple[b:]] + S[b][Tuple[:b]] ) # We save the optimal strategy corresponding to the triangle with leaves Tuple return S[n][tuple(L)], C[n][tuple(L)] # def evaluate_strategy(EVAL, S_in, T_in, ST_in, E, P, L, strategy, n): ''' evaluate_strategy(): primes; output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is E / <P> ''' ramifications = [] moves = [ 0 ] # moves: this list determines whether an isogeny construction must be performed k = 0 # k: current element of the strategy ramifications.append(list(P)) E_i = list(E) if EVAL: # Public points to be evaluated S_out = list( S_in ) # x(S) should be a torsion point with not common factors in L T_out = list( T_in ) # x(T) should be a torsion point with not common factors in L ST_out = list( ST_in ) # x(S - T) should be a torsion point with not common factors in L else: S_out = None T_out = None ST_out = None assert len(strategy) == (n - 1) for i in range(len(strategy)): pos = global_L.index( L[n - 1 - i]) # Current element of global_L to be required # Reaching the vertex (n - 1 - i, i) # Vertical edges (scalar multiplications) prev = sum(moves) while prev < (n - 1 - i): moves.append( strategy[k]) # Number of vertical edges to be performed T = list(ramifications[-1]) # New ramification for j in range(prev, prev + strategy[k], 1): T = curve.xMUL(T, E_i, global_L.index(L[j])) ramifications.append(list(T)) prev += strategy[k] k += 1 # Deciding which velu variant will be used if formula.name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if tuned: formula.set_parameters_velu(formula.sJ_list[pos], formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in formula.KPs, formula.xISOG, and formula.xEVAL if global_L[pos] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(global_L[pos] - 1) / 2.0)) c = int(floor((global_L[pos] - 1.0) / (4.0 * b))) if formula.name != 'tvelu': formula.set_parameters_velu(b, c, pos) # Kernel Points computation formula.KPs(ramifications[-1], E_i, pos) # Isogeny construction ramifications[-1][0], E_i[0] = fp.fp2_cswap( ramifications[-1][0], E_i[0], global_L[pos] == 4) ramifications[-1][1], E_i[1] = fp.fp2_cswap( ramifications[-1][1], E_i[1], global_L[pos] == 4) C_i = formula.xISOG(E_i, pos) ramifications[-1][0], E_i[0] = fp.fp2_cswap( ramifications[-1][0], E_i[0], global_L[pos] == 4) ramifications[-1][1], E_i[1] = fp.fp2_cswap( ramifications[-1][1], E_i[1], global_L[pos] == 4) # Now, we proceed by perform horizontal edges (isogeny evaluations) for j in range(0, len(moves) - 1, 1): if (formula.name == 'tvelu' or (formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND) or (global_L[pos] == 4)): ramifications[j] = formula.xEVAL(ramifications[j], pos) else: ramifications[j] = formula.xEVAL(ramifications[j], E_i) if EVAL: # Evaluating public points if (formula.name == 'tvelu' or (formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND) or (global_L[pos] == 4)): S_out = formula.xEVAL(S_out, pos) T_out = formula.xEVAL(T_out, pos) ST_out = formula.xEVAL(ST_out, pos) else: S_out = formula.xEVAL(S_out, E_i) T_out = formula.xEVAL(T_out, E_i) ST_out = formula.xEVAL(ST_out, E_i) # Updating the Montogmery curve coefficients E_i = [list(C_i[0]), list(C_i[1])] moves.pop() ramifications.pop() pos = global_L.index( L[0]) # Current element of global_L to be required if formula.name != 'tvelu': # This branchs corresponds with the use of the new velu's formulaes if tuned: formula.set_parameters_velu(formula.sJ_list[pos], formula.sI_list[pos], pos) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in formula.KPs, formula.xISOG, and formula.xEVAL if global_L[pos] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(global_L[pos] - 1) / 2.0)) c = int(floor((global_L[pos] - 1.0) / (4.0 * b))) formula.set_parameters_velu(b, c, pos) # Kernel Points computations formula.KPs(ramifications[0], E_i, pos) # Isogeny construction ramifications[0][0], E_i[0] = fp.fp2_cswap(ramifications[0][0], E_i[0], global_L[pos] == 4) ramifications[0][1], E_i[1] = fp.fp2_cswap(ramifications[0][1], E_i[1], global_L[pos] == 4) C_i = formula.xISOG(E_i, pos) ramifications[0][0], E_i[0] = fp.fp2_cswap(ramifications[0][0], E_i[0], global_L[pos] == 4) ramifications[0][1], E_i[1] = fp.fp2_cswap(ramifications[0][1], E_i[1], global_L[pos] == 4) if EVAL: # Evaluating public points if (formula.name == 'tvelu' or (formula.name == 'hvelu' and global_L[pos] <= formula.HYBRID_BOUND) or (global_L[pos] == 4)): S_out = formula.xEVAL(S_out, pos) T_out = formula.xEVAL(T_out, pos) ST_out = formula.xEVAL(ST_out, pos) else: S_out = formula.xEVAL(S_out, E_i) T_out = formula.xEVAL(T_out, E_i) ST_out = formula.xEVAL(ST_out, E_i) # Updating the Montogmery curve coefficients E_i = [list(C_i[0]), list(C_i[1])] return E_i, S_out, T_out, ST_out def pubkey_A(sk_a): nonlocal PB_a, QB_a, PQB_a Ra = curve.Ladder3pt(sk_a, PA, QA, PQA, A) pk_a, PB_a, QB_a, PQB_a = evaluate_strategy(True, PB, QB, PQB, A, Ra, curve.SIDp[::-1], Sp, len(curve.SIDp)) #a_curve = curve.coeff(pk_a) return pk_a def pubkey_B(sk_b): nonlocal PA_b, QA_b, PQA_b Rb = curve.Ladder3pt(sk_b, PB, QB, PQB, A) pk_b, PA_b, QA_b, PQA_b = evaluate_strategy(True, PA, QA, PQA, A, Rb, curve.SIDm[::-1], Sm, len(curve.SIDm)) #b_curve = curve.coeff(pk_b) return pk_b def dh_A(sk_a, pk_b): # sk here is alice's secret key # pk_b here is from bob (not processed by coeff) nonlocal PA_b, QA_b, PQA_b RB_a = curve.Ladder3pt(sk_a, PA_b, QA_b, PQA_b, pk_b) ss_a, _, _, _ = evaluate_strategy(False, PB, QB, PQB, pk_b, RB_a, curve.SIDp[::-1], Sp, len(curve.SIDp)) #ss_a_curve = curve.coeff(ss_a) #return ss_a_curve return ss_a def dh_B(sk_b, pk_a): # sk_b here is bob's secret key # pk_a here is from alice (not processed by coeff) nonlocal PB_a, QB_a, PQB_a RA_b = curve.Ladder3pt(sk_b, PB_a, QB_a, PQB_a, pk_a) ss_b, _, _, _ = evaluate_strategy(False, PA, QA, PQA, pk_a, RA_b, curve.SIDm[::-1], Sm, len(curve.SIDm)) #ss_b_curve = curve.coeff(ss_b) #return ss_b_curve return ss_b f_name = 'data/strategies/bsidh-' + prime + '-' + formula.name + tuned_name try: f = open(resource_filename('sidh', f_name)) # Corresponding to the list of Small Isogeny Degree, Lp := [l_0, ..., # l_{n-1}] [We need to include case l=2 and l=4] tmp = f.readline() tmp = [int(b) for b in tmp.split()] Sp = list(tmp) # Corresponding to the list of Small Isogeny Degree, Lm := [l_0, ..., # l_{n-1}] tmp = f.readline() tmp = [int(b) for b in tmp.split()] Sm = list(tmp) f.close() except IOError: print("// Strategies to be computed") # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to # include case l=2 and l=4] Sp, Cp = dynamic_programming_algorithm(SIDp[::-1], len(SIDp)) # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}] Sm, Cm = dynamic_programming_algorithm(SIDm[::-1], len(SIDm)) f = open(f_name, 'w') f.writelines(' '.join([str(tmp) for tmp in Sp]) + '\n') f.writelines(' '.join([str(tmp) for tmp in Sm]) + '\n') f.close() #print( # "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The curve.measures are given in millions of field operations.\n" # % (SQR, ADD) #) return attrdict(locals())
def csidh_suitable_bounds(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] L = algo.params.L n = algo.params.n strategy_block_cost = algo.gae.strategy_block_cost measure = algo.curve.measure security = algo.gae.security basis = algo.gae.basis print_intv = lambda v, n: ', '.join(list(map(format, v, ['2d'] * n))) # The next function computes the set \mu() given in the paper def neighboring_intvec(seq_i, L, IN, OUT): if OUT[2] >= 256.00: return OUT else: minimum = IN if measure(IN[1]) >= measure(OUT[1]): for j in seq_i: current_cost, _, _, _, _ = strategy_block_cost( L, OUT[0] + basis[j]) tmp = neighboring_intvec( seq_i, L, IN, ( OUT[0] + basis[j], current_cost, security(OUT[0] + basis[j], len(L)), ), ) if measure(minimum[1]) >= measure(tmp[1]): minimum = tmp return minimum # Finally, the next functions is the implementation of algorithm 2.0 def optimal_bounds(L, b, r): n = len(L) RNC, _, _, _, _ = strategy_block_cost(L, b) SEC = security(b, n) e = b # for i in range(n-1,-1,-1): for i in range(n): # The algorithm proceed by looking the best bounds when e_i <- e_i - 1 seq_i = [k for k in range(n) if k != i] (e_tmp, RNC_tmp, SEC_tmp) = (e, RNC, SEC) if e[i] > r: # Set the new possible optimal bounds temporal_cost, _, _, _, _ = strategy_block_cost( L, e - r * basis[i]) (e_tmp, RNC_tmp, SEC_tmp) = neighboring_intvec( seq_i, L, (e, RNC, SEC), ( e - r * basis[i], temporal_cost, security(e - r * basis[i], n), ), ) (e, RNC, SEC) = min( [(e_tmp, RNC_tmp, SEC_tmp), (e, RNC, SEC)], key=lambda t: measure(t[1]), ) # -------------------------------------------------------------------------------------------------- f = open(tmp_dir + setting.style + ".out", "a") f.write("decreasing: e_{" + print_intv([i], 1) + "}" + ", and increasing each e_j with j != " + print_intv([i], 1) + "; current optimal running-time: %7.3f\n" % measure(RNC)) f.write("[" + print_intv(e, n) + "]\n") f.close() # -------------------------------------------------------------------------------------------------- print("decreasing: e_{" + print_intv([i], 1) + "}" + ", and increasing each e_j with j != " + print_intv([i], 1) + "; current optimal running-time: %7.3f" % measure(RNC)) print("[" + print_intv(e, n) + "]\n") print("Security in bits ~ %f\n" % SEC) return (e, RNC) ''' ------------------------------------------------------------------------------------- Number of degree-(l_i) isogeny constructions to be performed: m_i ------------------------------------------------------------------------------------- ''' # ========================================================================== if setting.prime == 'p512': if setting.style == 'wd1': # __________________________________________________________________________ # ====== [MCR style, CSIDH-512] m = 10 if setting.style == 'wd2': # __________________________________________________________________________ # ====== [OAYT style, CSIDH-512] m = 5 if setting.style == 'df': # __________________________________________________________________________ # ====== [dummy-free style, CSIDH-512] m = 10 if setting.prime == 'p1024': if setting.style == 'wd1': # __________________________________________________________________________ # ====== [MCR style, CSIDH-1024] m = 3 if setting.style == 'wd2': # __________________________________________________________________________ # ====== [OAYT style, CSIDH-1024] m = 2 if setting.style == 'df': # __________________________________________________________________________ # ====== [dummy-free style, CSIDH-1024] m = 3 if setting.prime == 'p1792': if setting.style == 'wd1': # __________________________________________________________________________ # ====== [MCR style, CSIDH-1024] m = 2 if setting.style == 'wd2': # __________________________________________________________________________ # ====== [OAYT style, CSIDH-1024] m = 1 if setting.style == 'df': # __________________________________________________________________________ # ====== [dummy-free style, CSIDH-1024] m = 2 # --- k = 3 # Next integer vector bount is given in Onuki et al. manuscript print( "\n_______________________________________________________________________________________________________________________________" ) print("List of small odd primes") printl("L", L[::-1], n // k) print("\nInitial integer vector of bounts (b_0, ..., b_%d)" % n) e = [m] * (n - 1) + [(3 * m) // 2] e = numpy.array(e) printl("e", e, n // k) RUNNING_TIME, _, _, _, _ = strategy_block_cost(L[::-1], e) print( "// Number of field operations (GAE):\t%1.6f x M + %1.6f x S + %1.6f x a := %1.6f x M" % ( RUNNING_TIME[0] / (10.0**6), RUNNING_TIME[1] / (10.0**6), RUNNING_TIME[2] / (10.0**6), measure(RUNNING_TIME) / (10.0**6), )) print("\tSecurity ~ %f\n" % security(e, n)) print( "_______________________________________________________________________________________________________________________________" ) print("We proceed by searching a better integer vector of bounds\n") f = open(tmp_dir + setting.style + ".out", "w") f.write("\n") f.close() r = 1 for k in range(1, int(ceil((1.0 * m) / (1.0 * r)))): e, RNC = optimal_bounds(L[::-1], e, r) f = open(tmp_dir + setting.style + ".out", "a") f.write("\n") f.close() print( "_______________________________________________________________________________________________________________________________\n" ) return attrdict(name='csidh-suitable-bounds', **locals())
def Tvelu(curve): global_L = curve.global_L prime = curve.prime fp = curve.fp p = curve.p np = curve.np nm = curve.nm Em = curve.Em Ep = curve.Ep random = SystemRandom() poly_mul = Poly_mul(curve) poly_redc = Poly_redc(poly_mul) cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)]) cISOG = lambda l: numpy.array([ (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0), (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0), (3.0 * l - 7.0 + isequal[l == 3] * 6.0), ]) C_xEVAL = list( map(cEVAL, global_L)) # list of the costs of each degree-l isogeny evaluation C_xISOG = list(map( cISOG, global_L)) # list of the costs of each degree-l isogeny construction K = None def yDBL(P, A): ''' ------------------------------------------------------------------------- yDBL() input : a projective Twisted Edwards y-coordinate point y(P) := YP/WP, and the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the projective Twisted Edwards y-coordinate point y([2]P) ------------------------------------------------------------------------- ''' t_0 = fp.fp2_sqr(P[0]) t_1 = fp.fp2_sqr(P[1]) Z = fp.fp2_mul(A[1], t_0) X = fp.fp2_mul(Z, t_1) t_1 = fp.fp2_sub(t_1, t_0) t_0 = fp.fp2_mul(A[0], t_1) Z = fp.fp2_add(Z, t_0) Z = fp.fp2_mul(Z, t_1) return [fp.fp2_sub(X, Z), fp.fp2_add(X, Z)] def yADD(P, Q, PQ): ''' ------------------------------------------------------------------------- yADD() input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP, y(Q) := YQ/WQ, and y(P-Q) := YPQ/QPQ output: the projective Twisted Edwards y-coordinate point y(P+Q) ------------------------------------------------------------------------- ''' a = fp.fp2_mul(P[1], Q[0]) b = fp.fp2_mul(P[0], Q[1]) c = fp.fp2_add(a, b) d = fp.fp2_sub(a, b) c = fp.fp2_sqr(c) d = fp.fp2_sqr(d) xD = fp.fp2_add(PQ[1], PQ[0]) zD = fp.fp2_sub(PQ[1], PQ[0]) X = fp.fp2_mul(zD, c) Z = fp.fp2_mul(xD, d) return [fp.fp2_sub(X, Z), fp.fp2_add(X, Z)] def KPs_t(P, A, i): ''' ------------------------------------------------------------------------- KPs() input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP, the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, and a positive integer 0 <= i < n output: the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1 ------------------------------------------------------------------------- ''' assert curve.isinfinity(P) == False nonlocal K d = (global_L[i] - 1) // 2 K = [[[0, 0], [0, 0]] for j in range(d + 1)] K[0] = list([fp.fp2_sub(P[0], P[1]), fp.fp2_add(P[0], P[1])]) # 2a if global_L[i] == 3: K[1] = list([list(K[0]), list(K[1])]) else: K[1] = yDBL(K[0], A) # 4M + 2S + 4a for j in range(2, d, 1): K[j] = yADD(K[j - 1], K[0], K[j - 2]) # 4M + 2S + 6a return K # 2(l - 3)M + (l - 3)S + 3(l - 3)a def xISOG_t(A, i): ''' ------------------------------------------------------------------------------ xISOG() input : the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n output: the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E ------------------------------------------------------------------------------ ''' nonlocal K l = global_L[i] # l bits_of_l = bitlength(l) # Number of bits of L[i] d = (l - 1) // 2 # Here, l = 2d + 1 By = K[0][0] Bz = K[0][1] for j in range(1, d, 1): By = fp.fp2_mul(By, K[j][0]) Bz = fp.fp2_mul(Bz, K[j][1]) bits_of_l -= 1 constant_d_edwards = fp.fp2_sub(A[0], A[1]) # 1a tmp_a = A[0] tmp_d = constant_d_edwards # left-to-right method for computing a^l and d^l for j in range(1, bits_of_l + 1): tmp_a = fp.fp2_sqr(tmp_a) tmp_d = fp.fp2_sqr(tmp_d) if ((l >> (bits_of_l - j)) & 1) != 0: tmp_a = fp.fp2_mul(tmp_a, A[0]) tmp_d = fp.fp2_mul(tmp_d, constant_d_edwards) for j in range(3): By = fp.fp2_sqr(By) Bz = fp.fp2_sqr(Bz) C0 = fp.fp2_mul(tmp_a, Bz) C1 = fp.fp2_mul(tmp_d, By) C1 = fp.fp2_sub(C0, C1) return [C0, C1] # (l - 1 + 2*HW(l) - 2)M + 2(|l|_2 + 1)S + 2a def xEVAL_t(P, i): ''' ------------------------------------------------------------------------------ xEVAL() input : the projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n output: the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E ------------------------------------------------------------------------------ ''' nonlocal K d = (global_L[i] - 1) // 2 # Here, l = 2d + 1 Q0 = fp.fp2_add(P[0], P[1]) Q1 = fp.fp2_sub(P[0], P[1]) R0, R1 = curve.CrissCross(K[0][1], K[0][0], Q0, Q1) for j in range(1, d, 1): T0, T1 = curve.CrissCross(K[j][1], K[j][0], Q0, Q1) R0 = fp.fp2_mul(T0, R0) R1 = fp.fp2_mul(T1, R1) R0 = fp.fp2_sqr(R0) R1 = fp.fp2_sqr(R1) X = fp.fp2_mul(P[0], R0) Z = fp.fp2_mul(P[1], R1) return [X, Z] # 2(l - 1)M + 2S + (l + 1)a def xISOG_4(P): """ Degree-4 isogeny construction """ nonlocal K K = [[0, 0], [0, 0], [0, 0]] K[1] = fp.fp2_sub(P[0], P[1]) K[2] = fp.fp2_add(P[0], P[1]) K[0] = fp.fp2_sqr(P[1]) K[0] = fp.fp2_add(K[0], K[0]) C24 = fp.fp2_sqr(K[0]) K[0] = fp.fp2_add(K[0], K[0]) A24 = fp.fp2_sqr(P[0]) A24 = fp.fp2_add(A24, A24) A24 = fp.fp2_sqr(A24) return [A24, C24] def xEVAL_4(Q): """ Degree-4 isogeny evaluation """ t0 = fp.fp2_add(Q[0], Q[1]) t1 = fp.fp2_sub(Q[0], Q[1]) XQ = fp.fp2_mul(t0, K[1]) ZQ = fp.fp2_mul(t1, K[2]) t0 = fp.fp2_mul(t0, t1) t0 = fp.fp2_mul(t0, K[0]) t1 = fp.fp2_add(XQ, ZQ) ZQ = fp.fp2_sub(XQ, ZQ) t1 = fp.fp2_sqr(t1) ZQ = fp.fp2_sqr(ZQ) XQ = fp.fp2_add(t0, t1) t0 = fp.fp2_sub(ZQ, t0) XQ = fp.fp2_mul(XQ, t1) ZQ = fp.fp2_mul(ZQ, t0) return [XQ, ZQ] def KPs(P, A, i): if global_L[i] != 4: return KPs_t(P, A, i) def xISOG(A, i): if global_L[i] != 4: return xISOG_t(A, i) else: # A should corresponds with an order-4 point return xISOG_4(A) def xEVAL(P, i): if global_L[i] != 4: return xEVAL_t(P, i) else: return xEVAL_4(P) def cISOG_and_cEVAL(): """ Get cost of the isogeny constructions and evaluations """ nonlocal C_xISOG nonlocal C_xEVAL # E[p + 1] # First, we look for a full torsion point A = [[0x8, 0x0], [0x4, 0x0]] # Reading public generators points f = open(resource_filename('sidh', 'data/gen/' + prime)) # x(PA), x(QA) and x(PA - QA) PQA = f.readline() PQA = [int(x, 16) for x in PQA.split()] PA = [list(PQA[0:2]), [0x1, 0x0]] QA = [list(PQA[2:4]), [0x1, 0x0]] PQA = [list(PQA[4:6]), [0x1, 0x0]] # x(PB), x(QB) and x(PB - QB) PQB = f.readline() PQB = [int(x, 16) for x in PQB.split()] PB = [list(PQB[0:2]), [0x1, 0x0]] QB = [list(PQB[2:4]), [0x1, 0x0]] PQB = [list(PQB[4:6]), [0x1, 0x0]] f.close() for i in range(0, Ep[0] - 1, 1): PA = curve.xMUL(PA, A, 0) QA = curve.xMUL(QA, A, 0) PQA = curve.xMUL(PQA, A, 0) # Random kernels for counting the T_p = curve.Ladder3pt(random.randint(0, p - 1), PA, QA, PQA, A) T_m = curve.Ladder3pt(random.randint(0, p - 1), PB, QB, PQB, A) for i in range(0, np, 1): for j in range(0, Ep[i] - 1, 1): T_p = curve.xMUL(T_p, A, i) for i in range(np, np + nm, 1): for j in range(0, Em[i - np] - 1, 1): T_m = curve.xMUL(T_m, A, i) for i in range(0, np, 1): # Getting an orderl-l point Tp = list(T_p) for j in range(i + 1, np, 1): Tp = curve.xMUL(Tp, A, j) # Cost of xISOG() and KPs() fp.fp.set_zero_ops() KPs(Tp, A, i) t = fp.fp.get_ops() C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) fp.fp.set_zero_ops() Tp[0], A[0] = fp.fp2_cswap(Tp[0], A[0], global_L[i] == 4) Tp[1], A[1] = fp.fp2_cswap(Tp[1], A[1], global_L[i] == 4) B = xISOG(A, i) Tp[0], A[0] = fp.fp2_cswap(Tp[0], A[0], global_L[i] == 4) Tp[1], A[1] = fp.fp2_cswap(Tp[1], A[1], global_L[i] == 4) t = fp.fp.get_ops() C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # xEVAL: kernel point determined by the next isogeny evaluation fp.fp.set_zero_ops() T_p = xEVAL(T_p, i) # Cost of xEVAL fp.fp.set_zero_ops() T_m = xEVAL(T_m, i) t = fp.fp.get_ops() C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # Updating the new next curve A = list(B) # E[p - 1] # First, we look for a full torsion point A = [[0x8, 0x0], [0x4, 0x0]] T_m = curve.Ladder3pt(random.randint(0, p - 1), PA, QA, PQA, A) T_p = curve.Ladder3pt(random.randint(0, p - 1), PB, QB, PQB, A) for i in range(np, np + nm, 1): # Getting an orderl-l point Tp = list(T_p) for j in range(i + 1, np + nm, 1): Tp = curve.xMUL(Tp, A, j) # Cost of xISOG() and KPs() fp.fp.set_zero_ops() KPs(Tp, A, i) t = fp.fp.get_ops() C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) fp.fp.set_zero_ops() B = xISOG(A, i) t = fp.fp.get_ops() C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # xEVAL: kernel point determined by the next isogeny evaluation fp.fp.set_zero_ops() T_p = xEVAL(T_p, i) # Cost of xEVAL fp.fp.set_zero_ops() T_m = xEVAL(T_m, i) t = fp.fp.get_ops() C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # Updating the new next curve A = list(B) fp.fp.set_zero_ops() return None # Now, we proceed to store all the correct costs cISOG_and_cEVAL() return attrdict(name='tvelu', **locals())
def csidh_sdacs(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m exponent_of_two = algo.curve.exponent_of_two validation_stop = algo.curve.validation_stop SDACS = algo.curve.SDACS # --- k = 3 # Number of rows (format of the list) # --- print("#ifndef _SDACS_%s_H_" % setting.prime) print("#define _SDACS_%s_H_" % setting.prime) print("") assert n == len(L) print( "#define cofactor %d\t// Exponent of 2 in the factorization of (p + 1) " % exponent_of_two ) print("") print("#ifdef _MONT_C_CODE_") print( "#define UPPER_BOUND %d\t// Bits of 4 * sqrt( [p + 1] / [2^e] )" % validation_stop ) print( "\n// Recall, the list of Small Odd Primes (SOPs) is stored such that l_0 < l_1 < ... < l_{n-1}" ) assert n == len(SDACS) print( "\n// The list of Shortest Differential Addition Chains (SDACs) corresponding with each l_i" ) printl( "static int LENGTHS[]", [len(sdac_i) for sdac_i in SDACS], n // k + 1 ) print("") for i in range(0, n, 1): if len(SDACS[i]) == 0: print("static char SDAC%d[1];" % i) else: print( 'static char SDAC' + str(i) + '[] = \"' + ''.join([str(sdac_ij) for sdac_ij in SDACS[i]]) + '\";' ) SDACS_string = "static char *SDACs[N] = {\n\t" for i in range(0, n - 1, 1): if (i + 1) % (n // k + 1) == 0: SDACS_string = SDACS_string + "SDAC%d, \n\t" % (i) else: SDACS_string = SDACS_string + "SDAC%d, " % (i) SDACS_string = SDACS_string + "SDAC%d\n\t};" % (n - 1) print("") print(SDACS_string) print("#endif") print( "\n#endif /* required framework for the SDACs, which is used in CSIDH-%s */" % setting.prime[1:] ) return attrdict(name='csidh-sdacs', **locals())
def Svelu(curve, tuned, multievaluation): fp = curve.fp poly_mul = Poly_mul(curve) poly_redc = Poly_redc(poly_mul) global_L = curve.L prime = curve.prime SCALED_REMAINDER_TREE = multievaluation n = parameters["csidh"][prime]["n"] cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)]) cISOG = lambda l: numpy.array([ (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0), (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0), (3.0 * l - 7.0 + isequal[l == 3] * 6.0), ]) C_xEVAL = list( map(cEVAL, global_L)) # list of the costs of each degree-l isogeny evaluation C_xISOG = list(map( cISOG, global_L)) # list of the costs of each degree-l isogeny construction # Global variables to be used in KPs, xISOG, and xEVAL # Here, J is a set of cardinality sJ J = None sJ = None # Here, ptree_I corresponds with the product tree determined by I, and I is a set of cardinality sJ ptree_hI = None sI = (None, ) # Here, K is a set of cardinality sK K = None sK = None # An extra nonlocal variable which is used in xISOG and xEVAL XZJ4 = None # Next functions is used for setting the cardinalities sI, sJ, and sK def set_parameters_velu(b, c, i): nonlocal sJ nonlocal sI nonlocal sK assert b <= c # At this step, everythin is correct sJ = b sI = c d = ((global_L[i] - 2 - 4 * b * c - 1) // 2) + 1 assert d >= 0 sK = d return None def print_parameters_velu(): print("| sI: %3d, sJ: %3d, sK: %3d |" % (sI, sJ, sK), end="") return None # KPs computes x([i]P), x([j]P), and x([k]P) for each i in I, j in J, and j in J. # I, J, and K are defined according to Examples 4.7 and 4.12 of https://eprint.iacr.org/2020/341 def KPs(P, A, i): # Global variables to be used nonlocal J nonlocal sJ nonlocal ptree_hI nonlocal sI nonlocal K nonlocal sK # This functions computes all the independent data from the input of algorithm 2 of https://eprint.iacr.org/2020/341 if sI == 0: # Case global_L[i] = 3 is super very special case (nothing to do) J = [] ptree_hI = None K = [list(P)] # J, b, ptree_hI, c, K, d assert sJ == 0 and sI == 0 and sK == 1 return None # We need to ensure sI is greater than or equal sJ assert sI >= sJ # Additionally, sK should be greater than or equal to zero. If that is not the case, then sJ and sI were badly chosen assert sK >= 0 if sI == 1: # Branch corresponds with global_L[i] = 5 and global_L[i] = 7 # Recall sJ > 0, then sJ = 1 assert sJ == 1 P2 = curve.xDBL(P, A) J = [list(P)] I = [list(P2)] hI = [list([fp.fp_sub(0, P2[0]), P2[1]]) ] # we only need to negate x-coordinate of each point ptree_hI = poly_mul.product_tree(hI, sI) # product tree of hI if not SCALED_REMAINDER_TREE: # Using remainder trees ptree_hI = poly_redc.reciprocal_tree( { 'rpoly': [1], 'rdeg': 0, 'fpoly': [1], 'fdeg': 0, 'a': 1 }, 2 * sJ + 1, ptree_hI, sI, ) # reciprocal of the root is used to compute sons' reciprocals else: # Using scaled remainder trees assert (2 * sJ - sI + 1) > sI ptree_hI['reciprocal'], ptree_hI['a'] = poly_redc.reciprocal( ptree_hI['poly'][::-1], sI + 1, 2 * sJ - sI + 1) ptree_hI['scaled'], ptree_hI['as'] = ( list(ptree_hI['reciprocal'][:sI]), ptree_hI['a'], ) # Notice, 0 <= sK <= 1 assert sK <= 1 if sK == 1: K = [list(P2)] else: K = [] return None # At this step, sI > 1 assert sI > 1 if sJ == 1: # This branch corresponds with global_L[i] = 11 and global_L[i] = 13 Q = curve.xDBL(P, A) # x([2]P) Q2 = curve.xDBL(Q, A) # x([2]Q) J = [list(P)] I = [[0, 0]] * sI I[0] = list(Q) # x( Q) I[1] = curve.xADD(Q2, I[0], I[0]) # x([3]Q) for ii in range(2, sI, 1): I[ii] = curve.xADD(I[ii - 1], Q2, I[ii - 2]) # x([2**i + 1]Q) hI = [[fp.fp_sub(0, iP[0]), iP[1]] for iP in I ] # we only need to negate x-coordinate of each point ptree_hI = poly_mul.product_tree(hI, sI) # product tree of hI if not SCALED_REMAINDER_TREE: # Using remainder trees ptree_hI = poly_redc.reciprocal_tree( { 'rpoly': [1], 'rdeg': 0, 'fpoly': [1], 'fdeg': 0, 'a': 1 }, 2 * sJ + 1, ptree_hI, sI, ) # reciprocal of the root is used to compute sons' reciprocals else: # Using scaled remainder trees assert (2 * sJ - sI + 1) <= sI ptree_hI['scaled'], ptree_hI['as'] = poly_redc.reciprocal( ptree_hI['poly'][::-1], sI + 1, sI) ptree_hI['reciprocal'], ptree_hI['a'] = ( list(ptree_hI['scaled'][:(2 * sJ - sI + 1)]), ptree_hI['as'], ) # Notice, 0 <= sK <= 1 assert sK <= 1 if sK == 1: K = [list(Q)] else: K = [] return None # Now, we ensure sI >= sJ > 1 assert sJ > 1 # In other words, we can proceed by the general case # ------------------------------------------------ # Computing [j]P for each j in {1, 3, ..., 2*sJ - 1} J = [[0, 0]] * sJ J[0] = list(P) # x( P) P2 = curve.xDBL(P, A) # x([2]P) J[1] = curve.xADD(P2, J[0], J[0]) # x([3]P) for jj in range(2, sJ, 1): J[jj] = curve.xADD(J[jj - 1], P2, J[jj - 2]) # x([2*jj + 1]P) # ------------------------------------------------------- # Computing [i]P for i in { (2*sJ) * (2i + 1) : 0 <= i < sI} bhalf_floor = sJ // 2 bhalf_ceil = sJ - bhalf_floor P4 = curve.xDBL(P2, A) # x([4]P) P2[0], P4[0] = fp.fp_cswap(P2[0], P4[0], sJ % 2) # Constant-time swap P2[1], P4[1] = fp.fp_cswap( P2[1], P4[1], sJ % 2) # x([4]P) <--- coditional swap ---> x([2]P) Q = curve.xADD(J[bhalf_ceil], J[bhalf_floor - 1], P2) # Q := [2b]P P2[0], P4[0] = fp.fp_cswap(P2[0], P4[0], sJ % 2) # Constant-time swap P2[1], P4[1] = fp.fp_cswap( P2[1], P4[1], sJ % 2) # x([4]P) <--- coditional swap ---> x([2]P) I = [[0, 0]] * sI I[0] = list(Q) # x( Q) Q2 = curve.xDBL(Q, A) # x([2]Q) I[1] = curve.xADD(Q2, I[0], I[0]) # x([3]Q) for ii in range(2, sI, 1): I[ii] = curve.xADD(I[ii - 1], Q2, I[ii - 2]) # x([2**i + 1]Q) # -------------------------------------------------------------- # Computing [k]P for k in { 4*sJ*sI + 1, ..., l - 6, l - 4, l - 2} K = [[0, 0]] * sK if sK >= 1: K[0] = list(P2) # x([l - 2]P) = x([-2]P) = x([2]P) if sK >= 2: K[1] = list(P4) # x([l - 4]P) = x([-4]P) = x([4]P) for k in range(2, sK, 1): K[k] = curve.xADD(K[k - 1], P2, K[k - 2]) # ------------------------------------------------------------------------------------------------------ # ~~~~~~~~ ~~~~~~~~ # | | | | # Computing h_I(W) = | | (W - x([i]P)) = | | (Zi * W - Xi) / Zi where x([i]P) = Xi/Zi # i in I i in I # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates hI = [[fp.fp_sub(0, iP[0]), iP[1]] for iP in I] # we only need to negate x-coordinate of each point ptree_hI = poly_mul.product_tree(hI, sI) # product tree of hI if not SCALED_REMAINDER_TREE: # Using scaled remainder trees ptree_hI = poly_redc.reciprocal_tree( { 'rpoly': [1], 'rdeg': 0, 'fpoly': [1], 'fdeg': 0, 'a': 1 }, 2 * sJ + 1, ptree_hI, sI, ) # reciprocal of the root is used to compute sons' reciprocals else: # Using scaled remainder trees if sI < (2 * sJ - sI + 1): ptree_hI['reciprocal'], ptree_hI['a'] = poly_redc.reciprocal( ptree_hI['poly'][::-1], sI + 1, 2 * sJ - sI + 1) ptree_hI['scaled'], ptree_hI['as'] = ( list(ptree_hI['reciprocal'][:sI]), ptree_hI['a'], ) else: ptree_hI['scaled'], ptree_hI['as'] = poly_redc.reciprocal( ptree_hI['poly'][::-1], sI + 1, sI) ptree_hI['reciprocal'], ptree_hI['a'] = ( list(ptree_hI['scaled'][:(2 * sJ - sI + 1)]), ptree_hI['as'], ) # Polynomial D_j of algorithm 2 from https://eprint.iacr.org/2020/341 is not required, # but we need some some squares and products determined by list J # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates # Ensuring the cardinality of each ser coincide with the expected one assert len(I) == sI assert len(J) == sJ assert len(K) == sK return None # Next function perform algorithm 2 of https://eprint.iacr.org/2020/341 with input \alpha = 1 and \alpha = -1, and # then it computes the isogenous Montgomery curve coefficient def xISOG(A, i): nonlocal J nonlocal sJ nonlocal ptree_hI nonlocal sI nonlocal K nonlocal sK nonlocal XZJ4 AA = fp.fp_add(A[0], A[0]) # 2A' + 4C AA = fp.fp_sub(AA, A[1]) # 2A' AA = fp.fp_add(AA, AA) # 4A' # Polynomial D_j of algorithm 2 from https://eprint.iacr.org/2020/341 is not required, # but we need some some squares and products determined by list J # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates SUB_SQUARED = [0 for j in range(0, sJ, 1)] # ADD_SQUARED = [0 for j in range(0, sJ, 1)] # # List XZJ4 is required for degree-l isogeny evaluations... XZJ4 = [0 for j in range(0, sJ, 1)] # 2*Xj*Zj for j in range(0, sJ, 1): SUB_SQUARED[j] = fp.fp_sub(J[j][0], J[j][1]) # (Xj - Zj) SUB_SQUARED[j] = fp.fp_sqr(SUB_SQUARED[j]) # (Xj - Zj)^2 ADD_SQUARED[j] = fp.fp_add(J[j][0], J[j][1]) # (Xj + Zj) ADD_SQUARED[j] = fp.fp_sqr(ADD_SQUARED[j]) # (Xj + Zj)^2 XZJ4[j] = fp.fp_sub(SUB_SQUARED[j], ADD_SQUARED[j]) # -4*Xj*Zj # -------------------------------------------------------------------------------------------------- # ~~~~~~~~ # | | # Computing E_J(W) = | | [ F0(W, x([j]P)) * alpha^2 + F1(W, x([j]P)) * alpha + F2(W, x([j]P)) ] # j in J # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates # In particular, for a degree-l isogeny construction, we need alpha = 1 and alpha = -1 # EJ_0 is the one determined by alpha = 1 EJ_0 = [[0, 0, 0] for j in range(0, sJ, 1)] # EJ_1 is the one determined by alpha = -1 EJ_1 = [[0, 0, 0] for j in range(0, sJ, 1)] for j in range(0, sJ, 1): # However, each SUB_SQUARED[j] and ADD_SQUARED[j] should be multiplied by C tadd = fp.fp_mul(ADD_SQUARED[j], A[1]) tsub = fp.fp_mul(SUB_SQUARED[j], A[1]) # We require the double of tadd and tsub tadd2 = fp.fp_add(tadd, tadd) tsub2 = fp.fp_add(tsub, tsub) t1 = fp.fp_mul(XZJ4[j], AA) # A *(-4*Xj*Zj) # Case alpha = 1 linear = fp.fp_sub( t1, tadd2) # A *(-4*Xj*Zj) - C * (2 * (Xj + Zj)^2) EJ_0[j] = [tsub, linear, tsub] # Case alpha = -1 linear = fp.fp_sub( tsub2, t1) # C * (2 * (Xj - Zj)^2) - A *(-4*Xj*Zj) EJ_1[j] = [tadd, linear, tadd] # The faster way for multiplying is using a divide-and-conquer approach poly_EJ_0 = poly_mul.product_selfreciprocal_tree( EJ_0, sJ)['poly'] # product tree of EJ_0 (we only require the root) poly_EJ_1 = poly_mul.product_selfreciprocal_tree( EJ_1, sJ)['poly'] # product tree of EJ_1 (we only require the root) if not SCALED_REMAINDER_TREE: # Remainder tree computation remainders_EJ_0 = poly_redc.multieval_unscaled( poly_EJ_0, 2 * sJ + 1, ptree_hI, sI) remainders_EJ_1 = poly_redc.multieval_unscaled( poly_EJ_1, 2 * sJ + 1, ptree_hI, sI) else: # Approach using scaled remainder trees if ptree_hI != None: poly_EJ_0 = poly_redc.poly_redc(poly_EJ_0, 2 * sJ + 1, ptree_hI) fg_0 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI, poly_EJ_0[::-1], sI) remainders_EJ_0 = poly_redc.multieval_scaled( fg_0[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI) poly_EJ_1 = poly_redc.poly_redc(poly_EJ_1, 2 * sJ + 1, ptree_hI) fg_1 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI, poly_EJ_1[::-1], sI) remainders_EJ_1 = poly_redc.multieval_scaled( fg_1[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI) else: remainders_EJ_0 = [] remainders_EJ_1 = [] # Multipying all the remainders r0 = poly_mul.product(remainders_EJ_0, sI) r1 = poly_mul.product(remainders_EJ_1, sI) # --------------------------------------------------------------------------------- # Now, we proceed by computing the missing part which is determined by K # Notice, the denominators are the same and then they annulled between them # In other words, it is not required to compute the product of all Zk's with k In K # Case alpha = 1 hK_0 = [[fp.fp_sub(K[k][1], K[k][0])] for k in range(0, sK, 1)] hK_0 = poly_mul.product(hK_0, sK) # product of (Zk - Xk) for each k in K # Case alpha = -1 hK_1 = [[fp.fp_add(K[k][1], K[k][0])] for k in range(0, sK, 1)] hK_1 = poly_mul.product(hK_1, sK) # product of (Zk + Xk) for each k in K # -------------------------------------------------------------- # Now, we have all the ingredients for computing the image curve A24m = fp.fp_sub(A[0], A[1]) # A' - 2C A24 = fp.fp_exp(A[0], global_L[i]) # (A' + 2C)^l A24m = fp.fp_exp(A24m, global_L[i]) # (A' - 2C)^l t24m = fp.fp_mul( hK_1, r1 ) # output of algorithm 2 with alpha =-1 and without the demoninator t24m = fp.fp_sqr(t24m) # raised at 2 t24m = fp.fp_sqr(t24m) # raised at 4 t24m = fp.fp_sqr(t24m) # raised at 8 t24 = fp.fp_mul( hK_0, r0 ) # output of algorithm 2 with alpha = 1 and without the demoninator t24 = fp.fp_sqr(t24) # raised at 2 t24 = fp.fp_sqr(t24) # raised at 4 t24 = fp.fp_sqr(t24) # raised at 8 A24 = fp.fp_mul(A24, t24m) A24m = fp.fp_mul(A24m, t24) # Now, we have d = (A24m / A24) where the image Montgomery cuve coefficient is # B' 2*(1 + d) 2*(A24 + A24m) # B = ---- = --------- = -------------- # C (1 - d) (A24 - A24m) # However, we required B' + 2C = 4*A24 and 4C = 4 * (A24 - A24m) t24m = fp.fp_sub(A24, A24m) # (A24 - A24m) t24m = fp.fp_add(t24m, t24m) # 2*(A24 - A24m) t24m = fp.fp_add(t24m, t24m) # 4*(A24 - A24m) t24 = fp.fp_add(A24, A24) # 2 * A24 t24 = fp.fp_add(t24, t24) # 4 * A24 # return [t24, t24m], ptree_hI, XZJ4 return [t24, t24m] def xEVAL(P, A): AA = fp.fp_add(A[0], A[0]) # 2A' + 4C AA = fp.fp_sub(AA, A[1]) # 2A' AA = fp.fp_add(AA, AA) # 4A' # -------------------------------------------------------------------------------------------------- # ~~~~~~~~ # | | # Computing E_J(W) = | | [ F0(W, x([j]P)) * alpha^2 + F1(W, x([j]P)) * alpha + F2(W, x([j]P)) ] # j in J # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates # In particular, for a degree-l isogeny construction, we need alpha = 1 and alpha = -1 # EJ_0 is the one determined by alpha = x EJ_0 = [[0, 0, 0] for j in range(0, sJ, 1)] # Notice, the corresponding EJ_1 that is determined by alpha = 1/x can be computed by using EJ_0 XZ_add = fp.fp_add(P[0], P[1]) # X + Z XZ_sub = fp.fp_sub(P[0], P[1]) # X - Z AXZ2 = fp.fp_mul(P[0], P[1]) # X * Z t1 = fp.fp_sqr(P[0]) # X^2 t2 = fp.fp_sqr(P[1]) # Z^2 CX2Z2 = fp.fp_add(t1, t2) # X^2 + Z^2 CX2Z2 = fp.fp_mul(CX2Z2, A[1]) # C * (X^2 + Z^2) AXZ2 = fp.fp_add(AXZ2, AXZ2) # 2 * (X * Z) CXZ2 = fp.fp_mul(AXZ2, A[1]) # C * [2 * (X * Z)] AXZ2 = fp.fp_mul(AXZ2, AA) # A' * [2 * (X * Z)] for j in range(0, sJ, 1): XZj_add = fp.fp_add(J[j][0], J[j][1]) # Xj + Zj XZj_sub = fp.fp_sub(J[j][0], J[j][1]) # Xj - Zj t1 = fp.fp_mul(XZ_sub, XZj_add) # (X - Z) * (Xj + Zj) t2 = fp.fp_mul(XZ_add, XZj_sub) # (X + Z) * (Xj - Zj) # Computing the quadratic coefficient quadratic = fp.fp_sub(t1, t2) # 2 * [(X*Zj) - (Z*Xj)] quadratic = fp.fp_sqr(quadratic) # ( 2 * [(X*Zj) - (Z*Xj)] )^2 quadratic = fp.fp_mul(A[1], quadratic) # C * ( 2 * [(X*Zj) - (Z*Xj)] )^2 # Computing the constant coefficient constant = fp.fp_add(t1, t2) # 2 * [(X*Xj) - (Z*Zj)] constant = fp.fp_sqr(constant) # ( 2 * [(X*Xj) - (Z*Zj)] )^2 constant = fp.fp_mul(A[1], constant) # C * ( 2 * [(X*Xj) - (Z*Zj)] )^2 # Computing the linear coefficient # ---------------------------------------------------------------------------------------------------------- # C * [ (-2*Xj*Zj)*(alpha^2 + 1) + (-2*alpha)*(Xj^2 + Zj^2)] + [A' * (-2*Xj*Zj) * (2*X*Z)] where alpha = X/Z t1 = fp.fp_add(J[j][0], J[j][1]) # (Xj + Zj) t1 = fp.fp_sqr(t1) # (Xj + Zj)^2 t1 = fp.fp_add(t1, t1) # 2 * (Xj + Zj)^2 t1 = fp.fp_add( t1, XZJ4[j]) # 2 * (Xj + Zj)^2 - (4*Xj*Zj) := 2 * (Xj^2 + Zj^2) t1 = fp.fp_mul(t1, CXZ2) # [2 * (Xj^2 + Zj^2)] * (2 * [ C * (X * Z)]) t2 = fp.fp_mul(CX2Z2, XZJ4[j]) # [C * (X^2 + Z^2)] * (-4 * Xj * Zj) t1 = fp.fp_sub( t2, t1 ) # [C * (X^2 + Z^2)] * (-4 * Xj * Zj) - [2 * (Xj^2 + Zj^2)] * (2 * [ C * (X * Z)]) t2 = fp.fp_mul(AXZ2, XZJ4[j]) # (2 * [A' * (X * Z)]) * (-4 * Xj * Zj) linear = fp.fp_add( t1, t2) # This is our desired equation but multiplied by 2 linear = fp.fp_add( linear, linear) # This is our desired equation but multiplied by 4 # ---------------------------------------------------------------------------------------------------------- # Case alpha = X / Z EJ_0[j] = [constant, linear, quadratic] # The faster way for multiplying is using a divide-and-conquer approach poly_EJ_0 = poly_mul.product_tree( EJ_0, sJ)['poly'] # product tree of EJ_0 (we only require the root) poly_EJ_1 = list( poly_EJ_0[::-1]) # product tree of EJ_1(x) = x^{2b + 1} EJ_0(1/X) if not SCALED_REMAINDER_TREE: # Remainder tree computation remainders_EJ_0 = poly_redc.multieval_unscaled( poly_EJ_0, 2 * sJ + 1, ptree_hI, sI) remainders_EJ_1 = poly_redc.multieval_unscaled( poly_EJ_1, 2 * sJ + 1, ptree_hI, sI) else: # Approach using scaled remainder trees if ptree_hI != None: poly_EJ_0 = poly_redc.poly_redc(poly_EJ_0, 2 * sJ + 1, ptree_hI) fg_0 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI, poly_EJ_0[::-1], sI) remainders_EJ_0 = poly_redc.multieval_scaled( fg_0[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI) poly_EJ_1 = poly_redc.poly_redc(poly_EJ_1, 2 * sJ + 1, ptree_hI) fg_1 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI, poly_EJ_1[::-1], sI) remainders_EJ_1 = poly_redc.multieval_scaled( fg_1[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI) else: remainders_EJ_0 = [] remainders_EJ_1 = [] # Multipying all the remainders r0 = poly_mul.product(remainders_EJ_0, sI) r1 = poly_mul.product(remainders_EJ_1, sI) # --------------------------------------------------------------------------------- # Now, we proceed by computing the missing part which is determined by K # Notice, the denominators are the same and then they annulled between them # In other words, it is not required to compute the product of all Zk's with k In K hK_0 = [[0]] * sK hK_1 = [[0]] * sK for k in range(0, sK, 1): XZk_add = fp.fp_add(K[k][0], K[k][1]) # Xk + Zk XZk_sub = fp.fp_sub(K[k][0], K[k][1]) # Xk - Zk t1 = fp.fp_mul(XZ_sub, XZk_add) # (X - Z) * (Xk + Zk) t2 = fp.fp_mul(XZ_add, XZk_sub) # (X + Z) * (Xk - Zk) # Case alpha = X/Z hK_0[k] = [fp.fp_sub(t1, t2)] # 2 * [(X*Zk) - (Z*Xk)] # Case 1/alpha = Z/X hK_1[k] = [fp.fp_add(t1, t2)] # 2 * [(X*Xk) - (Z*Zk)] hK_0 = poly_mul.product(hK_0, sK) # product of (XZk - ZXk) for each k in K hK_1 = poly_mul.product(hK_1, sK) # product of (XXk - ZZk) for each k in K # --------------------------------------------------------------------------------- # Now, unifying all the computations XX = fp.fp_mul( hK_1, r1 ) # output of algorithm 2 with 1/alpha = Z/X and without the demoninator XX = fp.fp_sqr(XX) XX = fp.fp_mul(XX, P[0]) ZZ = fp.fp_mul( hK_0, r0 ) # output of algorithm 2 with alpha = X/Z and without the demoninator ZZ = fp.fp_sqr(ZZ) ZZ = fp.fp_mul(ZZ, P[1]) return [XX, ZZ] # Get cost of the isogeny constructions and evaluations sI_list = None sJ_list = None def cISOG_and_cEVAL(): nonlocal C_xISOG nonlocal C_xEVAL nonlocal sI_list nonlocal sJ_list if tuned: sI_list = [] sJ_list = [] try: f = open(resource_filename('sidh', 'data/ijk/' + prime)) except Exception as ex: raise Exception( "ijk data required for tuned mode not found: %r", ex) for i in range(0, n, 1): bc = f.readline() bc = [int(bci) for bci in bc.split()] sJ_list.append(bc[0]) sI_list.append(bc[1]) f.close() # First, we look for a full torsion point A = [2, 4] T_p, T_m = curve.full_torsion_points(A) for i in range(0, n, 1): if tuned: set_parameters_velu(sJ_list[i], sI_list[i], i) else: # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and xEVAL if global_L[i] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[i] - 1) / 2.0)) c = int(floor((global_L[i] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, i) # Getting an orderl-l point Tp = list(T_p) for j in range(i + 1, n, 1): Tp = curve.xMUL(Tp, A, j) # Cost of xISOG() and KPs() fp.set_zero_ops() KPs(Tp, A, i) t = fp.get_ops() C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) fp.set_zero_ops() B = xISOG(A, i) t = fp.get_ops() C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # xEVAL: kernel point determined by the next isogeny evaluation fp.set_zero_ops() T_p = xEVAL(T_p, A) # Cost of xEVAL fp.set_zero_ops() T_m = xEVAL(T_m, A) t = fp.get_ops() C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0]) # Updating the new next curve A = list(B) fp.set_zero_ops() assert curve.coeff(A) == 0x6 return None # Now, we proceed to store all the correct costs cISOG_and_cEVAL() return attrdict(name='svelu', **locals())
def Poly_redc(poly_mul): fp = poly_mul.fp poly_mul_middle = poly_mul.poly_mul_middle poly_mul_modxn = poly_mul.poly_mul_modxn def reciprocal(f, flen, n): """ This function computes the reciprocal of a given polynomial """ if flen < n: # truncated reciprocal return reciprocal(f + [0] * (n - flen), n, n) if n == 0: # Super special base case return [1], 1 if n == 1: # Base case when (1/f) mod x^n = 1 return [1], f[0] elif n == 2: # Base case when (1/f) = f_0 - f_1*x return [f[0], fp.fp_sub(0, f[1])], fp.fp_sqr(f[0]) elif n == 3: g, a = reciprocal(f[:2], 2, 2) t0 = fp.fp_sqr(f[1]) t1 = fp.fp_mul(f[0], f[2]) t2 = fp.fp_sub(t1, t0) t2 = fp.fp_mul(t2, f[0]) return ( [fp.fp_mul(g[0], a), fp.fp_mul(g[1], a), fp.fp_sub(0, t2)], fp.fp_sqr(a), ) elif n == 4: # This case gives the same cost as the general case g, a = reciprocal(f[:2], 2, 2) t0 = fp.fp_sqr(f[1]) t1 = fp.fp_mul(g[0], f[2]) t2 = fp.fp_mul(g[0], f[3]) t3 = fp.fp_mul(g[1], f[2]) t0 = fp.fp_sub(t1, t0) t1 = fp.fp_add(t2, t3) t2 = fp.fp_mul(t0, g[0]) t3 = fp.fp_mul(t0, g[1]) t4 = fp.fp_mul(t1, g[0]) t3 = fp.fp_add(t3, t4) return ( [ fp.fp_mul(g[0], a), fp.fp_mul(g[1], a), fp.fp_sub(0, t2), fp.fp_sub(0, t3), ], fp.fp_sqr(a), ) else: m = n - (n // 2) # Ceiling of n/2 # Recursively call to reciprocal g, a = reciprocal(f, flen, m) """ # Basic idea t = poly_mul_modxn(n, f[:n], n, g, m) # f * g mod x^n t = [fp_sub(t[0], a) ] + t[1:n] # (f * g - a) mod x^n assert( [0]*m == t[:m] ) # the first m coefficients are equal zero t = poly_mul_modxn(n - m, g, m, t[m:], n - m) # (f * g - a) * g mod x^n # Finally, the reciprocal is a*g - (f*g - a)*g mod x^n t = [fp.fp_mul(a, g[i]) for i in range(0, m, 1) ] + [fp.fp_sub(0, t[i]) for i in range(0, n - m, 1) ] """ # Basic idea but saving multiplication because of f*g - a is multiple of x^m t = poly_mul_middle( g, m, f[:n], n ) # f * g mod x^n (the last 'm >= n - m' coefficients) t = poly_mul_modxn( n - m, g, m, t[(2 * m - n) :], n - m ) # (f * g - a) * g mod x^n # Finally, the reciprocal is a*g - (f*g - a)*g mod x^n t = [fp.fp_mul(a, g[i]) for i in range(0, m, 1)] + [ fp.fp_sub(0, t[i]) for i in range(0, n - m, 1) ] return t, fp.fp_sqr(a) def poly_redc(h, hlen, f): """ Modular reduction in fp[x] with precomputation """ flen = f['deg'] + 1 if hlen < flen: # Base case, h(x) mod f(x) = h(x) return list(h) + [0] * (flen - hlen - 1) elif flen == 2 and hlen == 2: t0 = fp.fp_mul(h[0], f['poly'][1]) # h0 * f1 t1 = fp.fp_mul(h[1], f['poly'][0]) # h1 * f0 return [fp.fp_sub(t0, t1)] # f1 * (h0 + h1*x) mod (f0 + f1*x) elif flen == 2 and hlen == 3: f0_squared = fp.fp_sqr(f['poly'][0]) # f0^2 f1_squared = fp.fp_sqr(f['poly'][1]) # f1^2 t = fp.fp_sub(f['poly'][0], f['poly'][1]) # f0 - f1 t = fp.fp_sqr(t) # (f0 - f1)^2 t = fp.fp_sub(t, f0_squared) # (f0 - f1)^2 - f0^2 t = fp.fp_sub( t, f1_squared ) # (f0 - f1)^2 - f0^2 - f1^2 = -2*f0*f1 f0_squared = fp.fp_add(f0_squared, f0_squared) # 2*(f0^2) f1_squared = fp.fp_add(f1_squared, f1_squared) # 2*(f1^2) t0 = fp.fp_mul(f0_squared, h[2]) # [2*(f0^2)] * h2 t1 = fp.fp_mul(f1_squared, h[0]) # [2*(f1^2)] * h0 t2 = fp.fp_mul(t, h[1]) # [-2*f0*f1] * h1 return [ fp.fp_add(t0, fp.fp_add(t1, t2)) ] # [2 * (f1^2)] * (h0 + h1*x + h2*x^2) mod (f0 + f1*x) elif flen == 3 and hlen == 3: f2h1 = fp.fp_mul(f['poly'][2], h[1]) f2h0 = fp.fp_mul(f['poly'][2], h[0]) f1h2 = fp.fp_mul(f['poly'][1], h[2]) f0h2 = fp.fp_mul(f['poly'][0], h[2]) return [fp.fp_sub(f2h0, f0h2), fp.fp_sub(f2h1, f1h2)] """ elif flen == hlen: t0 = [fp.fp_mul(h[inner], f['poly'][fdeg - 1]) for inner in range(0, fdeg - 1, 1) ] t1 = [fp.fp_mul(h[fdeg - 1], f['poly'][inner]) for inner in range(0, fdeg - 1, 1) ] return [fp.fp_sub(t1[inner], t0[inner]) for inner in range(0, fdeg - 1, 1) ] """ else: H = [h[i] for i in range(hlen - 1, -1, -1)] # x^deg(h) * h(x^-1) Q = list(f['reciprocal']) # (1/F) mod x^(deg(f) - deg(h)) # (H/F) mod x^(deg(f) - deg(h)) Q = poly_mul_modxn( hlen - flen + 1, Q[: (hlen - flen + 1)], hlen - flen + 1, H[: (hlen - flen + 1)], hlen - flen + 1, ) q = [ Q[i] for i in range(hlen - flen, -1, -1) ] # x^deg(Q) * Q(x^-1) is the quotient qf = poly_mul_modxn( flen - 1, q, hlen - flen + 1, f['poly'], flen ) # (q * f) (x) has degree equals deg(h) # a*h(x) - (q * f)(x) will gives a polynomial of degree (deg(f) - 1) return [ fp.fp_sub(fp.fp_mul(f['a'], h[i]), qf[i]) for i in range(0, flen - 1, 1) ] def reciprocal_tree(r, glen, ptree_f, n): """ Reciprocal tree of a given product tree """ if n == 0: # Super special base case (nothing to do) return { 'left': None, 'right': None, 'poly': [1], 'deg': 0, 'reciprocal': [1], 'a': 1, } if n == 1: # We are in the leaf return { 'left': None, 'right': None, 'poly': list(ptree_f['poly']), 'deg': ptree_f['deg'], 'reciprocal': [1], 'a': 1, } if ptree_f['deg'] == 2 and (glen == 3): # Not required because of our redcution base cases return { 'left': ptree_f['left'], 'right': ptree_f['right'], 'poly': list(ptree_f['poly']), 'deg': ptree_f['deg'], 'reciprocal': [1], 'a': 1, } # At this point, we deal with a general case flen = ptree_f['deg'] + 1 # R, A = reciprocal(ptree_f['poly'][::-1], flen, glen - flen + 1 ) if r['rdeg'] <= (glen - flen): # This case requires a reciprocal computation R, A = reciprocal(ptree_f['poly'][::-1], flen, glen - flen + 1) else: # This case allows to use a polynomial multiplication modulo x^{glen - flen + 1} A = r['a'] R = poly_mul_modxn( glen - flen + 1, r['rpoly'], r['rdeg'] + 1, r['fpoly'][::-1], r['fdeg'] + 1, ) # Now, we proceed by recusion calls m = n - (n // 2) left = reciprocal_tree( { 'rpoly': R, 'rdeg': (glen - flen), 'fpoly': ptree_f['right']['poly'], 'fdeg': ptree_f['right']['deg'], 'a': A, }, flen - 1, ptree_f['left'], m, ) right = reciprocal_tree( { 'rpoly': R, 'rdeg': (glen - flen), 'fpoly': ptree_f['left']['poly'], 'fdeg': ptree_f['left']['deg'], 'a': A, }, flen - 1, ptree_f['right'], n - m, ) return { 'left': left, 'right': right, 'poly': list(ptree_f['poly']), 'deg': ptree_f['deg'], 'reciprocal': R, 'a': A, } def multieval_unscaled(g, glen, ptree_f, n): """ Next function computes g(x) mod f_1(x), ..., g(x) mod f_n(x) """ if n == 0: return [[1]] g_mod = poly_redc(g, glen, ptree_f) if n == 1: # Now, we have g corresponds with the initial G but now it is modulus a leaf of the product tree of f return [g_mod] else: m = n - (n // 2) # Reducing g(x) modulo the current node polynomial left = multieval_unscaled( g_mod, ptree_f['deg'], ptree_f['left'], m ) right = multieval_unscaled( g_mod, ptree_f['deg'], ptree_f['right'], n - m ) return left + right def multieval_scaled(g, glen, f, flen, ptree_f, n): """ Next functions computes the scaled remainder tree """ if n == 0: return [[1]] # fg = poly_mul_middle(f, flen, g, glen) if flen == n and glen == n and n > 1: fg = list(g) else: fg = poly_mul_middle(f, flen, g, glen) if n == 1: # The last coefficient should be the desire modular reduction with linear modulus if fg != []: return [[fg[-1]]] else: return [[1]] m = n - (n // 2) left = multieval_scaled( fg, flen, ptree_f['right']['poly'], ptree_f['right']['deg'] + 1, ptree_f['left'], m, ) right = multieval_scaled( fg, flen, ptree_f['left']['poly'], ptree_f['left']['deg'] + 1, ptree_f['right'], n - m, ) return left + right return attrdict(locals())
def csidh_test(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] full_torsion_points = algo.curve.full_torsion_points coeff = algo.curve.coeff p = algo.params.p global_L = L = algo.params.L n = algo.params.n A = algo.curve.A xMUL = algo.curve.xMUL if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu print_parameters_velu = algo.formula.print_parameters_velu sI = algo.formula.sI HYBRID_BOUND = algo.formula.HYBRID_BOUND get_ops = algo.fp.get_ops set_zero_ops = algo.fp.set_zero_ops KPs = algo.formula.KPs show_ops = algo.fp.show_ops xISOG = algo.formula.xISOG xEVAL = algo.formula.xEVAL total_cost = [0, 0, 0] print("p := 0x%X;" % p) print("fp := GF(p);") print("P<x> := PolynomialRing(fp);") print("E := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) if True: # T_p belongs to E[pi - 1] # T_m belongs to E[pi + 1] T_p, T_m = full_torsion_points(A) else: # T_m belongs to E[pi - 1] # T_p belongs to E[pi + 1] T_m, T_p = full_torsion_points(A) assert len(L) == n print( "// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment." ) for idx in range(0, n, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(idx + 1, n, 1): Tp = xMUL(Tp, A, i) print("// l:\t%7d |" % global_L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(sJ_list[idx], sI_list[idx], idx) else: # ------------------------------------------------------------- # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341 # These paramters are required in KPs, xISOG, and xEVAL if global_L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(global_L[idx] - 1) / 2.0)) c = int(floor((global_L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # KPs procedure set_zero_ops() KPs(Tp, A, idx) show_ops("Kps", 1.0, 0.0, False) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xISOG set_zero_ops() B = xISOG(A, idx) show_ops("xISOG", 1.0, 0.0, False) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] # ------------------------------------------------------------- # xEVAL: kernel point determined by the next isogeny evaluation set_zero_ops() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and global_L[idx] <= HYBRID_BOUND): T_p = xEVAL(T_p, idx) else: T_p = xEVAL(T_p, A) # xEVAL bench set_zero_ops() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and global_L[idx] <= HYBRID_BOUND): T_m = xEVAL(T_m, idx) else: T_m = xEVAL(T_m, A) show_ops("xEVAL", 1.0, 0.0, False) t = get_ops() total_cost[0] += t[0] total_cost[1] += t[1] total_cost[2] += t[2] print("|| cost: %7d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (global_L[idx] + 2.0))) # assert(validate(B)) A = list(B) # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) # print("assert(Random(B) * (p + 1) eq B!0);") # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1])) # print("assert(BOOL);") print( "\n// All the l_i's have been processed, output of xISOG corresponds with the given below" ) print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A)) print("assert(Random(B) * (p + 1) eq B!0);") print( "\n\"If no errors were showed using magma calculator, then all experiments were successful passed!\";" ) print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n") return attrdict(name='csidh-test', **locals())
def csidh_bounds(ctx): algo = ctx.meta['sidh.kwargs']['algo'] setting = ctx.meta['sidh.kwargs'] L = algo.params.L n = algo.params.n security = algo.gae.security strategy_block_cost = algo.gae.strategy_block_cost basis = numpy.eye(n, dtype=int) measure = algo.curve.measure print_intv = lambda v, n: ', '.join(list(map(format, v, ['2d'] * n))) # MITM procedure keyspace = 256.00 # For ensuring 128-bits of classical security # keyspace = 384.00 # For ensuring 192-bits of classical security # vOW Golden Collision Search # keyspace = 220.2295746338436 # For ensuring 128-bits of classical security # keyspace = 305.5629079671769 # For ensuring 192-bits of classical security # The next function computes the set \mu() given in the paper def neighboring_intvec(seq_i, L, IN, OUT): nonlocal keyspace if OUT[2] >= keyspace: return OUT else: minimum = IN if measure(IN[1]) >= measure(OUT[1]): for j in seq_i: current_cost, _, _, _, _ = strategy_block_cost( L, OUT[0] + basis[j] ) tmp = neighboring_intvec( seq_i, L, IN, ( OUT[0] + basis[j], current_cost, security(OUT[0] + basis[j], len(L)), ), ) if measure(minimum[1]) >= measure(tmp[1]): minimum = tmp return minimum # Finally, the next functions is the implementation of algorithm 2.0 def optimal_bounds(L, b, r): assert r >= 1 n = len(L) RNC, _, _, _, _ = strategy_block_cost(L, b) SEC = security(b, n) e = b for i in range(0, n, 1): # The algorithm proceed by looking the best bounds when e_i <- e_i - 1 seq_i = [k for k in range(n) if k != i] (e_tmp, RNC_tmp, SEC_tmp) = (e, RNC, SEC) if e[i] > r: # Set the new possible optimal bounds temporal_cost, _, _, _, _ = strategy_block_cost( L, e - r * basis[i] ) (e_tmp, RNC_tmp, SEC_tmp) = neighboring_intvec( seq_i, L, (e, RNC, SEC), ( e - r * basis[i], temporal_cost, security(e - r * basis[i], n), ), ) (e, RNC, SEC) = min( [(e_tmp, RNC_tmp, SEC_tmp), (e, RNC, SEC)], key=lambda t: measure(t[1]), ) print("[Security := %f]" % SEC, end="\t") print( "decreasing: e_{" + print_intv([i], 1) + "}" + ", and increasing each e_j with j != " + print_intv([i], 1) + "; current optimal running-time: %7.3f" % measure(RNC) ) print("[" + print_intv(e, n) + "]\n") # -------------------------------------------------------------------------------------------------- f = open( tmp_dir + "csidh_" + setting.prime + "_" + setting.style + "_m" + str(setting.benchmark) + ".py", "w", ) f.write('m = [' + ', '.join([str(ei) for ei in e[::-1]]) + ']') f.close() # -------------------------------------------------------------------------------------------------- return (e, RNC) ''' ------------------------------------------------------------------------------------- Number of degree-(l_i) isogeny constructions to be performed: m_i ------------------------------------------------------------------------------------- ''' # ========================================================================== m = setting.benchmark # --- k = 3 # Next integer vector bount is given in Onuki et al. manuscript print( "\n_______________________________________________________________________________________________________________________________" ) print("List of small odd primes") printl("L", L[::-1], n // k) print("\nInitial integer vector of bounts (b_0, ..., b_%d)" % n) if setting.benchmark == 1: if setting.style == 'wd1' or setting.style == 'df': assert n >= 221 e = [1] * 221 + [0] * (n - 221) else: assert n >= 139 e = [1] * 139 + [0] * (n - 139) # -------------------------------------------------------------------------------------------------- f = open( tmp_dir + "csidh_" + setting.prime + "_" + setting.style + "_m" + str(setting.benchmark) + ".py", "w", ) f.write('m = [' + ', '.join([str(ei) for ei in e]) + ']') f.close() # -------------------------------------------------------------------------------------------------- else: e = [m] * (n - 1) + [(3 * m) // 2] e = numpy.array(e) stop = False for i in range(0, n, 1): for j in range(0, m, 1): e = e - basis[i] if security(e, n) < keyspace: e = e + basis[i] stop = True break if stop: break printl("e", e, n // k) RUNNING_TIME, _, _, _, _ = strategy_block_cost(L[::-1], e) print( "// Number of field operations (GAE):\t%1.6f x M + %1.6f x S + %1.6f x a := %1.6f x M" % ( RUNNING_TIME[0] / (10.0 ** 6), RUNNING_TIME[1] / (10.0 ** 6), RUNNING_TIME[2] / (10.0 ** 6), measure(RUNNING_TIME) / (10.0 ** 6), ) ) print("\tSecurity ~ %f\n" % security(e, n)) print( "_______________________________________________________________________________________________________________________________" ) print("We proceed by searching a better integer vector of bounds\n") r = 1 for k in range(1, int(ceil((1.0 * m) / (1.0 * r)))): e, RNC = optimal_bounds(L[::-1], e, r) print( "_______________________________________________________________________________________________________________________________\n" ) return attrdict(name='bounds', **locals())