def main(ctx, **kwargs): """ \b ,-~~-.___. / | ' \\ ( ) 0 \_/-, ,----' ==== // / \-'~; /~~~(O) / __/~| / | =( _____| (_________| """ algo_args = kwargs.copy() algorithm = algo_args.pop('algorithm') algo_args.pop('benchmark') if algorithm == 'csidh': from sibc.csidh import CSIDH algo = CSIDH(**algo_args) elif algorithm == 'bsidh': from sibc.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['sibc.kwargs'] = attrdict(kwargs)
def csidh_precompute_strategy(ctx): """ Precomputation of optimal strategies """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] coeff = algo.curve.coeff p = algo.params.p L = algo.params.L m = algo.params.m n = algo.params.n SQR, ADD = algo.curve.SQR, algo.curve.ADD init_runtime = algo.field.init_runtime validate = algo.curve.issupersingular measure = algo.curve.measure GAE_at_0 = algo.gae.GAE_at_0 GAE_at_A = algo.gae.GAE_at_A strategy_block_cost = algo.gae.strategy_block_cost random_exponents = algo.gae.random_exponents print_exponents = algo.gae.print_exponents assert setting.uninitialized, 'option -u (--uninitialized) is required!' 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) bounds = '-diffbounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) bounds = '-samebounds' tuned = {True: '-tuned', False: ''}[setting.tuned] multievaluation = { True: 'scaled', False: 'unscaled' }[setting.multievaluation] # 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/" + algo.curve.model + '/csidh/' + 'csidh' + '-' + setting.prime + '-' + setting.style + '-e' + setting.exponent + bounds + '-' + setting.formula + '-' + multievaluation + tuned) file_path = resource_filename('sibc', file_path) 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() return attrdict(name='csidh-precompute-strategy', **locals())
def csidh_ijk(ctx): """ Velusqrt parameters as C-code headers files """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m sI_list = algo.formula.sI_list sJ_list = algo.formula.sJ_list # --- k = 3 # Number of rows (format of the list) # --- print("#ifndef _IJK_%s_H_" % setting.prime) print("#define _IJK_%s_H_" % setting.prime) print("") assert n == len(L) print("#ifdef _MONT_C_CODE_") print("// The list of the bitlength of each SOP") printl("static uint64_t bL[]", [bitlength(l) for l in L], n // k + 1) print("#endif") print("") print("#ifdef _ISOG_H_") print( "\n// The list of Small Odd Primes (SOPs) is stored such that l_0 < l_1 < ... < l_{n-1}" ) printl("static uint64_t L[]", L, n // k + 1) assert n == len(sI_list) assert n == len(sJ_list) sK_list = [] for i in range(0, n, 1): assert sJ_list[i] <= sI_list[i] sK_list.append(((L[i] - 2 - 4 * sJ_list[i] * sI_list[i] - 1) // 2) + 1) assert sK_list[i] >= 0 print("") print("#ifndef _C_CODE_") print( "// Sizes for the sets I, J, and K required in the new velusqrt formulae" ) printl("static int sizeI[]", sI_list, n // k + 1) printl("static int sizeJ[]", sJ_list, n // k + 1) printl("static int sizeK[]", sK_list, n // k + 1) print("#endif") print("") print("#define sI_max %d" % (max(sI_list))) print("#define sJ_max %d" % (max(sJ_list))) print("#define sK_max %d" % (L[n - 1] // 2 + 1)) print("#endif") print( "\n#endif /* required framework for the #I, #J, and #K, which is used in new velusqrt fomurlae on CSIDH-%s */" % setting.prime[1:]) return attrdict(name='ijk', **locals())
def bsidh_precompute_strategy(ctx): """ Precomputation of optimal strategies """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] SIDp = algo.strategy.SIDp SIDm = algo.strategy.SIDm assert setting.uninitialized, 'option -u (--uninitialized) is required!' tuned = {True: '-tuned', False: ''}[setting.tuned] multievaluation = { True: 'scaled', False: 'unscaled' }[setting.multievaluation] file_path = ("data/strategies/" + algo.curve.model + '/bsidh/' + 'bsidh' + '-' + setting.prime + '-' + setting.formula + '-' + multievaluation + tuned) file_path = resource_filename('sibc', file_path) 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 = algo.strategy.dynamic_programming_algorithm(SIDp[::-1], len(SIDp)) # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}] Sm, Cm = algo.strategy.dynamic_programming_algorithm(SIDm[::-1], len(SIDm)) f = open(file_path, 'w') f.writelines(' '.join([str(tmp) for tmp in Sp]) + '\n') f.writelines(' '.join([str(tmp) for tmp in Sm]) + '\n') f.close() return attrdict(name='bsidh-precompute-strategy', **locals())
def __init__( self, curvemodel, prime, formula, style, exponent, tuned, multievaluation, uninitialized, verbose, ): self.curvemodel = curvemodel self.prime = prime self.style = style self._exponent = exponent self.tuned = tuned self.uninitialized = uninitialized self.multievaluation = multievaluation self.params = attrdict(parameters['csidh'][prime]) self.params.update(self.params[style]) self.p_bytes = (self.params.p_bits + 8 - (self.params.p_bits % 8)) // 8 if self.curvemodel == 'montgomery': self.isogeny = MontgomeryIsogeny(formula, uninitialized=self.uninitialized) self.curve = MontgomeryCurve(prime) self.field = self.curve.field else: self.curve = None raise NotImplemented self.formula = self.isogeny(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 __init__( self, curvemodel, prime, formula, tuned, multievaluation, uninitialized, verbose ): self.params = attrdict(parameters['bsidh'][prime]) self.p_bytes = (self.params.p_bits + (self.params.p_bits % 8)) // 8 self.prime = prime self.tuned = tuned self.uninitialized = uninitialized self.multievaluation = multievaluation self.verbose = verbose random = SystemRandom() if curvemodel == 'montgomery': self.isogeny = MontgomeryIsogeny(formula, uninitialized = self.uninitialized) self.curve = MontgomeryCurve(prime) self.field = self.curve.field self.basefield = self.curve.field.basefield else: self.curve = None raise NotImplemented self.formula = self.isogeny(self.curve, self.tuned, self.multievaluation) if self.formula is not None and self.curve is not None: self.strategy = Strategy(prime, self.tuned, self.curve, self.formula) else: self.strategy = None raise NotImplemented
def csidh_precompute_parameters(ctx): """ Precomputation of tuned velusqrt parameters """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m generators = algo.curve.generators set_parameters_velu = algo.formula.set_parameters_velu init_runtime = algo.field.init_runtime kps = algo.formula.kps xisog = algo.formula.xisog xeval = algo.formula.xeval sJ_list = algo.formula.sJ_list xmul = algo.curve.xmul HYBRID_BOUND = algo.formula.HYBRID_BOUND A = [algo.curve.field(2), algo.curve.field(4)] if True: # T_p belongs to E[pi - 1] # T_m belongs to E[pi + 1] T_p, T_m = generators(A) else: # T_m belongs to E[pi - 1] # T_p belongs to E[pi + 1] T_m, T_p = generators(A) assert len(L) == n original_stdout = sys.stdout # Save a reference to the original standard output multievaluation = { True: 'scaled', False: 'unscaled' }[setting.multievaluation] path = resource_filename( 'sibc', "data/ijk/" + algo.curve.model + '/' + algo.curve.name + '-' + multievaluation) with open(path, 'w') as f: sys.stdout = f # Change the standard output to the file we created. 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 L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((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((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # kps procedure init_runtime() kps(Tp, A, idx) total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd # ------------------------------------------------------------- # xisog init_runtime() B = xisog(A, idx) total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd # ------------------------------------------------------------- # xeval bench init_runtime() if L[idx] <= HYBRID_BOUND: Tm = xeval(T_m, idx) else: Tm = xeval(T_m, A) total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd # assert(validate(B)) parameters[str(idx)].append( (b, c, [algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd], total_cost[0] + total_cost[1])) if 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='csidh-precompute-parameters', **locals())
def csidh_header(ctx): """ Optimal strategies as C-code headers files """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m strategy_block_cost = algo.gae.strategy_block_cost basis = numpy.eye(n, dtype=int) measure = algo.curve.measure # ========================================================================== 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) bounds = '-diffbounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) bounds = '-samebounds' # 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/" + algo.curve.model + '/' + 'csidh/csidh' + '-' + setting.prime + '-' + setting.style + '-e' + setting.exponent + bounds + '-' + setting.formula + '-' + algo.formula.multievaluation_name + algo.formula.tuned_name) file_path = resource_filename('sibc', file_path) f = open(file_path) 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() 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("#include <inttypes.h>\n\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)) # Need empty line at EOF for -Wnewline-eof print("") return attrdict(name='header', **locals())
def bsidh_precompute_parameters(ctx): """ Precomputation of tuned velusqrt parameters """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] p = algo.params.p np = algo.params.np Ep = algo.params.Ep nm = algo.params.nm Em = algo.params.Em Lp = algo.params.Lp Lm = algo.params.Lm L = list(Lp + Lm) A = [algo.curve.field(8), algo.curve.field(4)] xmul = algo.curve.xmul isinfinity = algo.curve.isinfinity coeff = algo.curve.coeff isfullorder = algo.curve.isfullorder cofactor_multiples = algo.curve.cofactor_multiples Ladder3pt = algo.curve.Ladder3pt if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu print_parameters_velu = algo.formula.print_parameters_velu HYBRID_BOUND = algo.formula.HYBRID_BOUND init_runtime_field = algo.field.init_runtime show_runtime_field = algo.field.show_runtime init_runtime_basefield = algo.basefield.init_runtime show_runtime_basefield = algo.basefield.show_runtime kps = algo.formula.kps xisog = algo.formula.xisog xeval = algo.formula.xeval field = algo.field random = SystemRandom() # ---Generators in E[p + 1] PA = list(algo.strategy.PA) QA = list(algo.strategy.QA) PQA = list(algo.strategy.PQA) # ---Generators in E[p - 1] PB = list(algo.strategy.PB) QB = list(algo.strategy.QB) PQB = list(algo.strategy.PQB) S = list(PA) T = list(QA) ST = list(PQA) for i in range(0, np, 1): for idx in range(0, Ep[i], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) S = list(PB) T = list(QB) ST = list(PQB) for i in range(np, np + nm, 1): for idx in range(0, Em[i - np], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) # Case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isfullorder(cofactor_multiples(S, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(T, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(ST, A, range(0, np, 1))) # Case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))) # Three point ladder: case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, A) T_p = list(R) T_m = list(S) original_stdout = sys.stdout # Save a reference to the original standard output multievaluation = { True: 'scaled', False: 'unscaled' }[setting.multievaluation] path = resource_filename( 'sibc', "data/ijk/" + algo.curve.model + '/' + algo.curve.name + '-' + multievaluation) with open(path, 'w') as f: sys.stdout = f # Change the standard output to the file we created. parameters = dict() for idx in range(0, np, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(0, np, 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 L[idx] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) parameters[str(idx)] = [] b += 1 for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1): b -= 1 c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # kps procedure init_runtime_basefield() kps(Tp, A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xisog init_runtime_basefield() Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) B = xisog(A, idx) Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xEVAL bench init_runtime_basefield() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): Tm = xeval(T_m, idx) else: Tm = xeval(T_m, A) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd parameters[str(idx)].append((b, c, [ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ], total_cost[0] + total_cost[1])) if L[idx] <= 4: 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]) # -------------------------------------------------------------------------------------------------------------------- A = [algo.curve.field(8), algo.curve.field(4)] # Three point ladder: case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, A) T_p = list(R) T_m = list(S) for idx in range(np, np + nm, 1): # ------------------------------------------------------------- # Random kernel point Tp = list(T_p) for i in range(np, np + nm, 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 L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) parameters[str(idx)] = [] b += 1 for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1): b -= 1 c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) total_cost = [0, 0, 0] # ------------------------------------------------------------- # kps procedure init_runtime_basefield() kps(Tp, A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xisog init_runtime_basefield() B = xisog(A, idx) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd # ------------------------------------------------------------- # xEVAL bench init_runtime_basefield() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): Tm = xeval(T_m, idx) else: Tm = xeval(T_m, A) total_cost[0] += algo.basefield.fpmul total_cost[1] += algo.basefield.fpsqr total_cost[2] += algo.basefield.fpadd parameters[str(idx)].append((b, c, [ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ], total_cost[0] + total_cost[1])) if 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]) sys.stdout = original_stdout # Reset the standard output to its original value return attrdict(name='bsidh-precompute-parameters', **locals())
def plot_strategy(ctx): """ draw strategy graphs as a subgraph Discrete Right Triangle (DRT) """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] if setting.algorithm == 'csidh': L = algo.params.L m = algo.params.m n = algo.params.n 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) bounds = '-diffbounds' else: # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i) bounds = '-samebounds' # 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/" + algo.curve.model + '/' + 'csidh' + '-' + setting.prime + '-' + setting.style + '-e' + setting.exponent + bounds + '-' + setting.formula + '-' + algo.formula.multievaluation_name + algo.formula.tuned_name) file_path = resource_filename('sibc', file_path) f = open(file_path) 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() file_path = ('csidh' + '-' + setting.prime + '-' + setting.style + '-e' + setting.exponent + bounds + '-' + setting.formula + '-' + algo.formula.multievaluation_name + algo.formula.tuned_name + '-' + algo.curve.model) elif setting.algorithm == 'bsidh': file_path = ("data/strategies/" + algo.curve.model + '/' + 'bsidh' + '-' + setting.prime + '-' + setting.formula + '-' + algo.formula.multievaluation_name + algo.formula.tuned_name) file_path = resource_filename('sibc', file_path) f = open(file_path) S_out = [] # 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()] S_out.append(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()] S_out.append(list(tmp)) f.close() file_path = ('bsidh' + '-' + setting.prime + '-' + setting.style + '-' + setting.formula + '-' + algo.formula.multievaluation_name + algo.formula.tuned_name + '-' + algo.curve.model) else: print("only csidh and bsidh are implemented") click.Exit(1) # ---- 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 file_name = file_path + '-id' + str(idx) + '.png' plt.savefig(file_name) print("saving graph: " + file_name) # plt.show() plt.close() print( "// The strategies have been plotted and stored in the current directory" ) 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, [], [] return attrdict(name='plot-strategy', **locals())
def PolyMul(field, maxdeg=None, mindeg=64): # ---------------------------------------------------------------------------------- # Table of 2 raised to a negative power if maxdeg != None: inverse_of_two = (field(2)**-1) max_exp = 2 * int(floor(sqrt(maxdeg))) negative_powers_of_two = dict() negative_powers_of_two[1] = 1 j = 1 j_next = 1 for i in range(0, max_exp, 1): j_next *= 2 negative_powers_of_two[j_next] = (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( reduce(lambda x, y: x + ' + ' + y, [f'({h[i]}) * (x ^ {i})' for i in range(0, lenh, 1)])) return None def karatsuba_mul(f, flen, g, glen): """ Karatsuba style multiplication of two polynomials """ if flen < glen: return poly_mul(g, glen, f, flen) # At this step, we ensure flen >= glen if [] == g or [] == f: # Multiplication by 0, here the zero polynomial is represented as the empty list return [] if glen == 1: # Multipication by a constant return [(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]) c[0] = (f[0] * g[0]) # coeff of x^0 c[2] = (f[1] * g[1]) # coeff of x^2 f01 = (f[0] + f[1]) g01 = (g[0] + g[1]) c[1] = (f01 * g01) c[1] = (c[1] - c[0]) c[1] = (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]) # Thus c(x) = (f * g)(x) is a cubic polynomial c[0] = (f[0] * g[0]) # coeff of x^0 c[2] = (f[1] * g[1]) f01 = (f[0] + f[1]) g01 = (g[0] + g[1]) c[1] = (f01 * g01) c[1] = (c[1] - c[0]) c[1] = (c[1] - c[2]) # coeff of x^1 c[3] = (f[2] * g[1]) # coeff of x^3 f2g0 = (f[2] * g[0]) c[2] = (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 = (f[0] + f[1]) f_02 = (f[0] + f[2]) g_01 = (g[0] + g[1]) g_02 = (g[0] + g[2]) t_01 = (f_01 * g_01) t_02 = (f_02 * g_02) l_coeff = (t_01 - karatsuba_0[0]) l_coeff = (l_coeff - karatsuba_1[0]) q_coeff = (t_02 - karatsuba_0[0]) q_coeff = (q_coeff - karatsuba_1[2]) q_coeff = (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] + [(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 = [(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 = [(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 = [(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 = [ (fg_mid[i] - fg_high[i]) for i in range(0, mf + mg - 1, 1) ] + fg_mid[(mf + mg - 1):] fg_mid = [(fg_mid[i] - fg_low[i]) for i in range(0, 2 * nf - 1, 1)] + fg_mid[(2 * nf - 1):] return (fg_low[:nf] + [(fg_low[nf + i] + fg_mid[i]) for i in range(0, nf - 1, 1)] + [fg_mid[nf - 1]] + [(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] * (len(h_0) - len(h_1)) return [[(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) Gj.append(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.fp_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] * m) degy = 0 func = {0: (lambda x, y: x + y), 1: lambda x, y: x - y} 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 or will be needed hr = H[j][i] h[r] = func[q % 2](h[r], hr) deg += 1 degy += hm2 return [[(h[i] * negative_powers_of_two[n2]) for i in range(0, m, 1)]] 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 = [], [] # (-1)^q determines if an addition or substraction will be needed func = {0: (lambda x, y: x + y), 1: lambda x, y: x - y} 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(func[sgn](f[j][i], f[n2 + j][r])) Gj1.append(func[sgn](g[j][i], g[n2 + j][r])) # --- Fj2.append(func[1 - sgn](f[j][i], f[n2 + j][r])) Gj2.append(func[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((H1[j][i] + H2[j][i])) h2j.append(func[1 - sgn](0, (H1[j][r] - H2[j][r]))) h1.append(h1j) h2.append(h2j) return h1 + h2 if maxdeg != None: def poly_mul(f, flen, g, glen): ff = list(f) + [0] * (glen - flen) gg = list(g) + [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 > mindeg: n = 2**len(bin(2 * hlen - 1)[2:]) fg_qring = qring_mul([ff + [0] * (n - hlen)], [gg + [0] * (n - hlen)], 0) # return [ (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) else: def poly_mul(f, flen, g, glen): return karatsuba_mul(f, flen, g, glen) def poly_mul_modxn(n, 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 """ 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 [] return [] if [] == g or [] == f: # Multiplication by cero (we need to ensure the output has length n, the zero polynomial corresponds a list with n zeroes return [0] * n if n == 1: # Only the constant coefficients are required return [(f[0] * g[0])] if n >= (flen + glen - 1): # Karatsuba multiplication with no possible savings return poly_mul(f, flen, g, glen) + [0] * (n - flen - glen + 1) # At this step we ensure n < (flen + glen - 1) if glen == 1: return [(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 = (f[0] * g[0]) f0g1 = (f[0] * g[1]) f1g0 = (f[1] * g[0]) return [f0g0, (f0g1 + f1g0)] if n == 3: if glen == 2: # Multiplication modulo x^3 of a linear polynomial with another of degree at least 2 f0g0 = (f[0] * g[0]) f1g1 = (f[1] * g[1]) f01 = (f[0] + f[1]) g01 = (g[0] + g[1]) t01 = (f01 * g01) t01 = (t01 - f0g0) t01 = (t01 - f1g1) f2g0 = (f[2] * g[0]) return [f0g0, t01, (f1g1 + f2g0)] if glen == 3: c00 = (f[0] * g[0]) # coeff of x^0 c11 = (f[1] * g[1]) f01 = (f[0] + f[1]) g01 = (g[0] + g[1]) c01 = (f01 * g01) c01 = (c01 - c00) c01 = (c01 - c11) f02 = (f[0] + f[2]) g02 = (g[0] + g[2]) c02 = (f02 * g02) c22 = (f[2] * g[2]) c02 = (c02 - c00) c02 = (c02 - c22) c02 = (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] * S) nf = n // 2 # floor of n/2 nc = -(-n // 2) # Ceiling of n/2 for i in range(0, n, 1): c[i] = (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] = ((f[i] + f[i + j + 1]) * (g[i] + g[i + j + 1])) c[k] = (c[k] - (c[i] + c[i + j + 1])) k = k + 1 c[n - 1] = c[0] for i in range(1, nf, 1): for j in range(1, n - 2 * i, 1): c[n + 2 * i - 1 + j] = (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] = (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 = [f[2 * i + 0] for i in range(0, f0len, 1)] f1 = [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 = [g[2 * i + 0] for i in range(0, g0len, 1)] g1 = [g[2 * i + 1] for i in range(0, g1len, 1)] # Middle part like karatsuba f01 = [(f0[i] + f1[i]) for i in range(0, f1len, 1)] + f0[f1len:] g01 = [(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 = [(fg_01[i] - fg_0[i]) for i in range(0, n01, 1)] + fg_01[n01:] fg_01 = [(fg_01[i] - fg_1[i]) for i in range(0, n1, 1)] + fg_01[n1:] # Unifying the computations fg = [0] * n for i in range(0, n0, 1): fg[2 * i] = fg_0[i] for i in range(0, n01, 1): fg[2 * i + 1] = fg_01[i] for i in range(0, n1 - 1, 1): fg[2 * i + 2] = (fg[2 * i + 2] + fg_1[i]) if 2 * n1 < n: fg[2 * n1] = (fg[2 * n1] + fg_1[n1 - 1]) return fg def quasi_poly_mul_middle(g, glen, f, flen): """ Next function 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 """ # if not glen == len(g): # import pdb; pdb.set_trace(); assert glen == len(g) assert flen == len(f) if glen == 0: return [] if glen == 1: return [(g[0] * f[0])] glen0 = glen // 2 # floor(glen / 2) glen1 = glen - glen0 # ceil(glen / 2) A = poly_mul_middle( g[glen0:], glen1, [(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( [(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]] + [(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, [(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 [(A[i] - B[i]) for i in range(0, glen1, 1)] + [(C[i] + B[i]) for i in range(0, glen0, 1)] def poly_mul_middle(g, glen, f, flen): """ Next function computes the central part polynomial of f * g mod (x^m - 1) This functions is an extension of quasi_poly_mul_middle() """ 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], 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] + 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 [(fg_low[i] + fg_high[i]) for i in range(0, glen - 1, 1)] + [fg_high[glen - 1]] def poly_mul_selfreciprocal(g, glen, f, flen): """ 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 """ if glen == 0 and flen == 0: return [] if glen == 1 and flen == 1: return [(g[0] * f[0])] if glen == 2 and flen == 2: h = [0] * 3 h[0] = (g[0] * f[0]) h[1] = (h[0] + h[0]) h[2] = h[0] return h if glen == 3 and flen == 3: h = [0] * 5 h[0] = (g[0] * f[0]) h[2] = (g[1] * f[1]) g01 = (g[0] + g[1]) f01 = (f[0] + f[1]) h[1] = (g01 * f01) h[2] = (h[2] + h[0]) h[1] = (h[1] - h[2]) h[2] = (h[2] + h[0]) h[3] = h[1] h[4] = h[0] return h if glen == 4 and flen == 4: h = [0] * 7 h[0] = (g[0] * f[0]) h[3] = (g[1] * f[1]) g01 = (g[0] + g[1]) f01 = (f[0] + f[1]) h[2] = (g01 * f01) h[2] = (h[2] - h[0]) h[1] = (h[2] - h[3]) h[3] = (h[3] + h[0]) h[3] = (h[3] + h[3]) h[4] = h[2] h[5] = h[1] h[6] = h[0] return h if glen == 5 and flen == 5: h = [0] * 9 g10 = (g[1] - g[0]) f01 = (f[0] - f[1]) h[1] = (g10 * f01) g20 = (g[2] - g[0]) f02 = (f[0] - f[2]) h[2] = (g20 * f02) g21 = (g[2] - g[1]) f12 = (f[1] - f[2]) h[3] = (g21 * f12) h[0] = (g[0] * f[0]) g1f1 = (g[1] * f[1]) g2f2 = (g[2] * f[2]) t = (g1f1 + h[0]) h[1] = (h[1] + t) h[3] = (h[3] + h[1]) h[4] = (t + g2f2) h[4] = (h[4] + t) h[2] = (h[2] + t) h[2] = (h[2] + g2f2) h[3] = (h[3] + g1f1) h[3] = (h[3] + g2f2) h[5] = h[3] h[6] = h[2] h[7] = h[1] h[8] = 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] * len0 f0 = [0] * len0 g1 = [0] * len1 f1 = [0] * len1 # We proceed by applying the same idea as in poly_mul_modxn # --- for i in range(0, len0, 1): g0[i] = g[2 * i] f0[i] = f[2 * i] h0 = poly_mul_selfreciprocal(g0, len0, f0, len0) # --- for i in range(0, len1, 1): g1[i] = g[2 * i + 1] f1[i] = f[2 * i + 1] h1 = poly_mul_selfreciprocal(g1, len1, f1, len1) # --- for i in range(0, len1, 1): g0[i] = (g0[i] + g1[i]) f0[i] = (f0[i] + f1[i]) for i in range(0, len1, 1): g0[i + 1] = (g0[i + 1] + g1[i]) f0[i + 1] = (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] = (h01[i] - h0[i]) for i in range(0, 2 * len1 - 1, 1): h01[i] = (h01[i] - h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 1] = (h01[i + 1] - h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 1] = (h01[i + 1] - h1[i]) for i in range(0, 2 * len1 - 1, 1): h01[i + 2] = (h01[i + 2] - h1[i]) for i in range(1, 2 * len0 - 1, 1): h01[i] = (h01[i] - h01[i - 1]) # Unifying the computations hlen = 2 * glen - 1 h = [0] * hlen for i in range(0, 2 * len0 - 1, 1): h[2 * i] = h0[i] for i in range(0, 2 * len0 - 2, 1): h[2 * i + 1] = h01[i] for i in range(0, 2 * len1 - 1, 1): h[2 * i + 2] = (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] * (2 * glen - 1) for i in range(0, glen - 1, 1): h[i] = (h[i] + h0[i]) for i in range(0, glen - 1, 1): h[2 * glen - 2 - i] = (h[2 * glen - 2 - i] + h0[i]) for i in range(0, glen - 1, 1): h[half + i] = (h[half + i] + h1[i]) for i in range(0, glen - 1, 1): h[glen + half - 2 - i] = (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] * (hlen - m) for i in range(m, hlen, 1): h[i] = h[hlen - 1 - i] return h def product_tree(f, n): """ Product tree of given list of polynomials """ if n == 0: return {'left': None, 'right': None, 'poly': [1], 'deg': 0} if n == 1: # No multiplication is required return { 'left': None, 'right': None, 'poly': 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'], } def product_selfreciprocal_tree(f, n): """ Product tree of given list of self reciprocal polynomials """ if n == 0: return {'left': None, 'right': None, 'poly': [1], 'deg': 0} if n == 1: # No multiplication is required return { 'left': None, 'right': None, 'poly': 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 assert len(list_g_mod_f) == n out = list_g_mod_f[0][0] for j in range(1, n, 1): out = (out * list_g_mod_f[j][0]) return out return attrdict(locals())
def csidh_test(ctx): """ GF(p)-operation cost of kps, xisog, and xeval """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] generators = algo.curve.generators coeff = algo.curve.coeff p = algo.params.p L = algo.params.L n = algo.params.n A = [algo.curve.field(2), algo.curve.field(4)] 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 HYBRID_BOUND = algo.formula.HYBRID_BOUND init_runtime = algo.field.init_runtime kps = algo.formula.kps show_runtime = algo.field.show_runtime 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 + (%s) * 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 = generators(A) else: # T_m belongs to E[pi - 1] # T_p belongs to E[pi + 1] T_m, T_p = generators(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 |" % L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(algo.formula.sJ_list[idx], algo.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 L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # kps procedure init_runtime() kps(Tp, A, idx) show_runtime("kps") total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd # ------------------------------------------------------------- # xisog init_runtime() B = xisog(A, idx) show_runtime("xisog") total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd # ------------------------------------------------------------- # xeval: kernel point determined by the next isogeny evaluation init_runtime() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_p = xeval(T_p, idx) else: T_p = xeval(T_p, A) # xeval bench init_runtime() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_m = xeval(T_m, idx) else: T_m = xeval(T_m, A) show_runtime("xeval") total_cost[0] += algo.field.fpmul total_cost[1] += algo.field.fpsqr total_cost[2] += algo.field.fpadd print("|| cost: %7d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (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 + (%s) * 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 successfully passed!\";" ) print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n") return attrdict(name='csidh-test', **locals())
def bsidh_main(ctx): """ Random instance example of a key-exchange """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] coeff = algo.curve.coeff SQR, ADD = algo.curve.SQR, algo.curve.ADD init_runtime_basefield = algo.basefield.init_runtime init_runtime_field = algo.field.init_runtime validate = algo.curve.issupersingular get_A = algo.curve.get_A measure = algo.curve.measure strategy_at_6_A = algo.strategy.strategy_at_6_A strategy_at_6_B = algo.strategy.strategy_at_6_B strategy_A = algo.strategy.strategy_A strategy_B = algo.strategy.strategy_B random_scalar_A = algo.strategy.random_scalar_A random_scalar_B = algo.strategy.random_scalar_B print( "// The running time is assuming S = %1.2f x M and a = %1.2f x M, and giving in millions of field operations.\n" % (SQR, ADD)) ''' ------------------------------------------------------------------------------------- Main ------------------------------------------------------------------------------------- ''' # ------------------------------------------------------------------------- Alice print("// --- \033[0;35mAlice\033[0m") init_runtime_basefield() init_runtime_field() a_private = random_scalar_A() a_public = strategy_at_6_A(a_private) print( "// Running time (Strategy evaluation):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p);" % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²);" % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) print("sk_a := %d;" % a_private) print("pk_a := %s;" % coeff(a_public)) # ------------------------------------------------------------------------- Bob print("\n// --- \033[0;34mBob\033[0m") init_runtime_basefield() init_runtime_field() b_private = random_scalar_B() b_public = strategy_at_6_B(b_private) print( "// Running time (Strategy evaluation):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p);" % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²);" % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) print("sk_b := %d;" % b_private) print("pk_b := %s;" % coeff(b_public)) print( "\n// ===================== \033[0;33mSecret Sharing Computation\033[0m" ) # ------------------------------------------------------------------------- Alice print("// --- \033[0;35mAlice\033[0m") init_runtime_basefield() init_runtime_field() pk_b = get_A([b_public[0], algo.field(1)], [b_public[1], algo.field(1)], [b_public[2], algo.field(1)]) public_validation = validate(pk_b) assert public_validation print( "// Running time (supersingular test):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p)," % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²);" % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) init_runtime_basefield() init_runtime_field() ss_a = strategy_A(a_private, b_public) print( "// Running time (Strategy evaluation + supersingular test):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p)," % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²);" % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) print("ss_a := %s;\n" % coeff(ss_a)) # ------------------------------------------------------------------------- Bob print("// --- \033[0;34mBob\033[0m") init_runtime_basefield() init_runtime_field() pk_a = get_A([a_public[0], algo.field(1)], [a_public[1], algo.field(1)], [a_public[2], algo.field(1)]) public_validation = validate(pk_a) assert public_validation print( "// Running time (supersingular test):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p)," % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²)," % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) init_runtime_basefield() init_runtime_field() ss_b = strategy_B(b_private, a_public) print( "// Running time (Strategy evaluation + supersingular test):\n\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p)," % ( algo.basefield.fpmul / (10.0**6), algo.basefield.fpsqr / (10.0**6), algo.basefield.fpadd / (10.0**6), measure([ algo.basefield.fpmul, algo.basefield.fpsqr, algo.basefield.fpadd ]) / (10.0**6), )) print("\t%2.3fM + %2.3fS + %2.3fa = %2.3fM in GF(p²);" % ( algo.field.fp2mul / (10.0**6), algo.field.fp2sqr / (10.0**6), algo.field.fp2add / (10.0**6), measure([algo.field.fp2mul, algo.field.fp2sqr, algo.field.fp2add]) / (10.0**6), )) print("ss_b := %s;" % coeff(ss_b)) try: assert (coeff(ss_a) == coeff(ss_b)) print('\n\x1b[0;30;43m' + 'Successfully passed!' + '\x1b[0m') except: raise TypeError('\x1b[0;30;41m' + 'Great Scott!... The sky is falling. NOT PASSED!!!' + '\x1b[0m') return attrdict(name='bsidh-main', **locals())
def csidh_sdacs(ctx): """ SDACs as C-code headers files """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] L = algo.params.L n = algo.params.n m = algo.params.m exponent_of_two = (len(bin(algo.curve.cofactor)[2:]) - 1) 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) if (2**exponent_of_two ) == algo.curve.cofactor: 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 PolyRedc(polymul): poly_mul_middle= polymul.poly_mul_middle poly_mul_modxn = polymul.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], (0 - f[1])], (f[0] ** 2) elif n == 3: g, a = reciprocal(f[:2], 2, 2) t0 = (f[1] ** 2) t1 = (f[0] * f[2]) t2 = (t1 - t0) t2 = (t2 * f[0]) return ( [(g[0] * a), (g[1] * a), (0 - t2)], (a ** 2), ) elif n == 4: # This case gives the same cost as the general case g, a = reciprocal(f[:2], 2, 2) t0 = (f[1] ** 2) t1 = (g[0] * f[2]) t2 = (g[0] * f[3]) t3 = (g[1] * f[2]) t0 = (t1 - t0) t1 = (t2 + t3) t2 = (t0 * g[0]) t3 = (t0 * g[1]) t4 = (t1 * g[0]) t3 = (t3 + t4) return ( [ (g[0] * a), (g[1] * a), (0 - t2), (0 - t3), ], (a ** 2), ) 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 = [(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 = [(a * g[i]) for i in range(0, m, 1) ] + [(-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 = [(a * g[i]) for i in range(0, m, 1)] + [ (0 - t[i]) for i in range(0, n - m, 1) ] return t, (a ** 2) 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 = (h[0] * f['poly'][1]) # h0 * f1 t1 = (h[1] * f['poly'][0]) # h1 * f0 return [(t0 - t1)] # f1 * (h0 + h1*x) mod (f0 + f1*x) elif flen == 2 and hlen == 3: f0_squared = (f['poly'][0] ** 2) # f0^2 f1_squared = (f['poly'][1] ** 2) # f1^2 t = (f['poly'][0] - f['poly'][1]) # f0 - f1 t = (t ** 2) # (f0 - f1)^2 t = (t - f0_squared) # (f0 - f1)^2 - f0^2 t = ( t - f1_squared ) # (f0 - f1)^2 - f0^2 - f1^2 = -2*f0*f1 f0_squared = (f0_squared + f0_squared) # 2*(f0^2) f1_squared = (f1_squared + f1_squared) # 2*(f1^2) t0 = (f0_squared * h[2]) # [2*(f0^2)] * h2 t1 = (f1_squared * h[0]) # [2*(f1^2)] * h0 t2 = (t * h[1]) # [-2*f0*f1] * h1 return [ (t0 + (t1 + t2)) ] # [2 * (f1^2)] * (h0 + h1*x + h2*x^2) mod (f0 + f1*x) elif flen == 3 and hlen == 3: f2h1 = (f['poly'][2] * h[1]) f2h0 = (f['poly'][2] * h[0]) f1h2 = (f['poly'][1] * h[2]) f0h2 = (f['poly'][0] * h[2]) return [(f2h0 - f0h2), (f2h1 - f1h2)] """ elif flen == hlen: t0 = [(h[inner] * f['poly'][fdeg - 1]) for inner in range(0, fdeg - 1, 1) ] t1 = [(h[fdeg - 1] * f['poly'][inner]) for inner in range(0, fdeg - 1, 1) ] return [(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 [ ((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 bsidh_test(ctx): """ GF(p²)-operation cost of kps, xisog, and xeval """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] p = algo.params.p np = algo.params.np Ep = algo.params.Ep nm = algo.params.nm Em = algo.params.Em Lp = algo.params.Lp Lm = algo.params.Lm L = list(Lp + Lm) A = [algo.curve.field(8), algo.curve.field(4)] xmul = algo.curve.xmul isinfinity = algo.curve.isinfinity coeff = algo.curve.coeff isfullorder = algo.curve.isfullorder cofactor_multiples = algo.curve.cofactor_multiples Ladder3pt = algo.curve.Ladder3pt if algo.formula.name != 'tvelu': set_parameters_velu = algo.formula.set_parameters_velu print_parameters_velu = algo.formula.print_parameters_velu HYBRID_BOUND = algo.formula.HYBRID_BOUND init_runtime_field = algo.field.init_runtime show_runtime_field = algo.field.show_runtime init_runtime_basefield = algo.basefield.init_runtime show_runtime_basefield = algo.basefield.show_runtime kps = algo.formula.kps xisog = algo.formula.xisog xeval = algo.formula.xeval field = algo.field random = SystemRandom() print("p := 0x%X;" % p) print("fp := GF(p);") print("_<x> := PolynomialRing(fp);") print("fp2<u> := ext<fp | x^2 + 1>;") print("Pr<x> := PolynomialRing(fp2);") # ---Generators in E[p + 1] PA = list(algo.strategy.PA) QA = list(algo.strategy.QA) PQA = list(algo.strategy.PQA) # ---Generators in E[p - 1] PB = list(algo.strategy.PB) QB = list(algo.strategy.QB) PQB = list(algo.strategy.PQB) print("E := EllipticCurve(x^3 + (%s) * x^2 + x);" % coeff(A)) S = list(PA) T = list(QA) ST = list(PQA) for i in range(0, np, 1): for idx in range(0, Ep[i], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) print("\n// Verifying torsion-(p + 1) points") print("// x([p + 1]PA) = (1:0)?\t", isinfinity(S)) print("// x([p + 1]QA) = (1:0)?\t", isinfinity(T)) print("// x([p + 1]PQA) = (1:0)?\t", isinfinity(ST)) S = list(PB) T = list(QB) ST = list(PQB) for i in range(np, np + nm, 1): for idx in range(0, Em[i - np], 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) assert isinfinity(S) assert isinfinity(T) assert isinfinity(ST) print("\n// Verifying torsion-(p - 1) points") print("// x([p - 1]PB) = (1:0)?\t", isinfinity(S)) print("// x([p - 1]QB) = (1:0)?\t", isinfinity(T)) print("// x([p - 1]PQB) = (1:0)?\t", isinfinity(ST)) # Case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) print("\n// Verifying orders") assert isfullorder(cofactor_multiples(S, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(T, A, range(0, np, 1))) assert isfullorder(cofactor_multiples(ST, A, range(0, np, 1))) print( "// PA is a full order point?\t", isfullorder(cofactor_multiples(S, A, range(0, np, 1))), ) print( "// QA is a full order point?\t", isfullorder(cofactor_multiples(T, A, range(0, np, 1))), ) print( "// QPA is a full order point?\t", isfullorder(cofactor_multiples(ST, A, range(0, np, 1))), ) # Case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False assert isinfinity(ST) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) print("\n// Verifying orders") assert isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))) assert isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))) print( "// PB is a full order point?\t", isfullorder(cofactor_multiples(S, A, range(np, np + nm, 1))), ) print( "// QB is a full order point?\t", isfullorder(cofactor_multiples(T, A, range(np, np + nm, 1))), ) print( "// QPB is a full order point?\t", isfullorder(cofactor_multiples(ST, A, range(np, np + nm, 1))), ) # Three point ladder: case (p + 1) S = list(PA) T = list(QA) ST = list(PQA) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(0, np, 1): for idx in range(0, Ep[i] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, algo.curve.field(2)) T_p = list(R) T_m = list(S) 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 = xmul(Tp, A, i) print("// l:\t%7d |" % L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(algo.formula.sJ_list[idx], algo.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 L[idx] <= 4: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # kps procedure init_runtime_basefield() init_runtime_field() kps(Tp, A, idx) show_runtime_field("kps") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xisog init_runtime_basefield() init_runtime_field() Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) B = xisog(A, idx) Tp[0], A[0] = cswap(Tp[0], A[0], L[idx] == 4) Tp[1], A[1] = cswap(Tp[1], A[1], L[idx] == 4) show_runtime_field("xisog") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xeval: kernel point determined by the next isogeny evaluation init_runtime_basefield() init_runtime_field() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND) or (L[idx] == 4)): T_p = xeval(T_p, idx) else: T_p = xeval(T_p, A) # xeval bench init_runtime_basefield() init_runtime_field() if (setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND) or (L[idx] == 4)): T_m = xeval(T_m, idx) else: T_m = xeval(T_m, A) show_runtime_field("xeval") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (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("B := EllipticCurve(x^3 + (%s) * x^2 + x);" % coeff(A)) print("assert(Random(B) * (p + 1) eq B!0);") A = [algo.curve.field(8), algo.curve.field(4)] # Three point ladder: case (p - 1) S = list(PB) T = list(QB) ST = list(PQB) assert isinfinity(S) == False assert isinfinity(T) == False for i in range(np, np + nm, 1): for idx in range(0, Em[i - np] - 1, 1): S = xmul(S, A, i) T = xmul(T, A, i) ST = xmul(ST, A, i) k = random.randint(0, p) R = Ladder3pt(k, S, T, ST, algo.curve.field(2)) T_p = list(R) T_m = list(S) 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 = xmul(Tp, A, i) print("// l:\t%7d |" % L[idx], end="") total_cost = [0, 0, 0] if setting.formula != 'tvelu': if setting.tuned: set_parameters_velu(algo.formula.sJ_list[idx], algo.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 L[idx] == 3: b = 0 c = 0 else: b = int(floor(sqrt(L[idx] - 1) / 2.0)) c = int(floor((L[idx] - 1.0) / (4.0 * b))) set_parameters_velu(b, c, idx) print_parameters_velu() # ------------------------------------------------------------- # kps procedure init_runtime_basefield() init_runtime_field() kps(Tp, A, idx) show_runtime_field("kps") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xisog init_runtime_basefield() init_runtime_field() B = xisog(A, idx) show_runtime_field("xisog") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add # ------------------------------------------------------------- # xeval: kernel point determined by the next isogeny evaluation init_runtime_basefield() init_runtime_field() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_p = xeval(T_p, idx) else: T_p = xeval(T_p, A) # xeval bench init_runtime_basefield() init_runtime_field() if setting.formula == 'tvelu' or (setting.formula == 'hvelu' and L[idx] <= HYBRID_BOUND): T_m = xeval(T_m, idx) else: T_m = xeval(T_m, A) show_runtime_field("xeval") total_cost[0] += algo.field.fp2mul total_cost[1] += algo.field.fp2sqr total_cost[2] += algo.field.fp2add print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ") print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) / (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 + (%s) * 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 successfully passed!\";" ) print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n") return attrdict(name='bsidh-test', **locals())
def csidh_main(ctx): """ Random instance example of a key-exchange """ algo = ctx.meta['sibc.kwargs']['algo'] setting = ctx.meta['sibc.kwargs'] coeff = algo.curve.coeff p = algo.params.p L = algo.params.L m = algo.params.m n = algo.params.n SQR, ADD = algo.curve.SQR, algo.curve.ADD init_runtime = algo.field.init_runtime validate = algo.curve.issupersingular measure = algo.curve.measure GAE_at_0 = algo.gae.GAE_at_0 GAE_at_A = algo.gae.GAE_at_A random_exponents = algo.gae.random_exponents print_exponents = algo.gae.print_exponents print( "// The running time is assuming S = %1.2f x M and a = %1.2f x M, and giving in millions of field operations.\n" % (SQR, ADD) ) ''' ------------------------------------------------------------------------------------- Main ------------------------------------------------------------------------------------- ''' print_exponents("// Exponent bounds", m) print("\n// ===================== \033[0;33mPublic Key Generation\033[0m") # ------------------------------------------------------------------------- Alice print("// --- \033[0;35mAlice\033[0m") init_runtime() a_private = random_exponents(m) a_public = GAE_at_0(a_private) print( "// Running time (GAE):\t\t\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) print_exponents("sk_a", a_private) print("pk_a := %s;" % coeff(a_public)) # ------------------------------------------------------------------------- Bob print("\n// --- \033[0;34mBob\033[0m") init_runtime() b_private = random_exponents(m) b_public = GAE_at_0(b_private) print( "// Running time (GAE):\t\t\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) print_exponents("sk_b", b_private) print("pk_b := %s;" % coeff(b_public)) print("\n// ===================== \033[0;33mSecret Sharing Computation\033[0m") # ------------------------------------------------------------------------- Alice print("// --- \033[0;35mAlice\033[0m") init_runtime() public_validation = validate(b_public) assert public_validation print( "// Running time (key validation):\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) init_runtime() ss_a = GAE_at_A(a_private, b_public) print( "// Running time (GAE + key validation):\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) print("ss_a := %s;\n" % coeff(ss_a)) # ------------------------------------------------------------------------- Bob print("// --- \033[0;34mBob\033[0m") init_runtime() public_validation = validate(a_public) assert public_validation print( "// Running time (key validation):\t%2.3fM + %2.3fS + %2.3fa = %2.3fM," % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) init_runtime() ss_b = GAE_at_A(b_private, a_public) print( "// Running time (GAE + key validation):\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % ( algo.field.fpmul / (10.0 ** 6), algo.field.fpsqr / (10.0 ** 6), algo.field.fpadd / (10.0 ** 6), measure([algo.field.fpmul, algo.field.fpsqr, algo.field.fpadd]) / (10.0 ** 6), ) ) print("ss_b := %s;" % coeff(ss_b)) try: assert(coeff(ss_a) == coeff(ss_b)) print('\n\x1b[0;30;43m' + 'Successfully passed!' + '\x1b[0m') except: raise TypeError( '\x1b[0;30;41m' + 'Great Scott!... The sky is falling. NOT PASSED!!!' + '\x1b[0m' ) return attrdict(name='csidh-main', **locals())
def MontgomeryCurve(prime): name = prime model= 'montgomery' if prime in parameters['csidh'].keys(): # CSIDH only requires the factorization of p + 1 L = parameters['csidh'][prime]['L'] n = parameters['csidh'][prime]['n'] cofactor = parameters['csidh'][prime]['cofactor'] p = parameters['csidh'][prime]['p'] 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 field = PrimeField(p) elif prime in parameters['bsidh'].keys(): # B-SIDH only requires the factorization of p + 1 and p - 1 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'] L = list(Lp + Lm) n = len(L) p = parameters['bsidh'][prime]['p'] cofactor_p = (p + 1) // reduce(lambda x, y: (x * y), Lp) cofactor_m = (p - 1) // reduce(lambda x, y: (x * y), Lm) validation_stop = sum([bitlength(l_i) for l_i in Lp]) / 2.0 + 2 p_minus_one_halves = parameters['bsidh'][prime]['p_minus_one_halves'] p_minus_3_quarters = parameters['bsidh'][prime]['p_minus_3_quarters'] field = QuadraticField(p) else: assert False, "only CSIDH and B-SIDH are currently implemented" # Shortest Differential Addition Chains (SDACs) for each l_i path = resource_filename('sibc', "data/sdacs/" + prime) SDACS = filename_to_list_of_lists_of_ints(path) assert len(SDACS) > 0, f'There is file precomputed sdacs for {prime} prime' 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 def measure(x): """ Field additions, multiplications, and squarings 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] random = SystemRandom() 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 = (A[0] + A[0]) # (2 * A24) A4_squared = (A4_squared - A[1]) # (2 * A24) - C24 A4_squared = (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 = (A4_squared ** 2) # (A')^2 C4_squared = (A[1] ** 2) # (C')^2 t = (C4_squared + C4_squared) # 2 * [(C')^2] num = (C4_squared + t) # 3 * [(C')^2] num = (A4_squared - num) # (A')^2 - 3 * [(C')^2] s = (num ** 2) # { (A')^2 - 3 * [(C')^2] }^2 num = (num * s) # { (A')^2 - 3 * [(C')^2] }^3 C4_squared = (C4_squared ** 2) # (C')^4 den = (t + t) # 4 * [(C')^2] den = (A4_squared - den) # (A')^2 - 4 * [(C')^2] den = (den * C4_squared) # {(A')^2 - 4 * [(C')^2] } * [(C')^4] den = (den ** -1) # 1 / {(A')^2 - 4 * [(C')^2] } * [(C')^4] num = (num * den) # j := { (A')^2 - 3 * [(C')^2] }^3 / {(A')^2 - 4 * [(C')^2] } * [(C')^4] num = (num + num) # 2*j num = (num + num) # 4*j num = (num + num) # 8*j num = (num + num) # 16*j num = (num + num) # 32*j num = (num + num) # 64*j num = (num + num) # 128*j num = (num + num) # 256*j return num def elligator(A): """ elligator() samples two points on E[pi + 1] or E[pi - 1] """ Ap = (A[0] + A[0]) Ap = (Ap - A[1]) Ap = (Ap + Ap) Cp = A[1] u = field(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 = (0 - 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 affine_to_projective(affine): """ affine_to_projective() 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 + field(2), field(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 get_A(P, Q, PQ): """ ---------------------------------------------------------------------- coeff() input : the affine Montgomery x-coordinate points x(P) := (XP : YP), x(Q) := (XQ : ZQ), and x(P - Q) := (XPQ : ZPQ) on the curve E : y^2 = x^3 + (A/C)*x^2 + x output: the projective Montgomery coefficient (A + 2C : 4C) ---------------------------------------------------------------------- """ t0 = (P[0] + P[1]) # XP + ZP t1 = (Q[0] + Q[1]) # XQ + ZQ t = (t0 * t1) # (XP + ZP) * (XQ + ZQ) XPXQ = (P[0] * Q[0]) # XP * XQ ZPZQ = (P[1] * Q[1]) # ZP * ZQ t = (t - XPXQ) t = (t - ZPZQ) # XPZQ + ZPXQ s = (XPXQ - ZPZQ) # XPXQ - ZPZQ t0 = (t * PQ[0]) # (XPZQ + ZPXQ) * XPQ t1 = (s * PQ[1]) # (XPXQ - ZPZQ) * ZPQ t0 = (t0 + t1) # (XPZQ + ZPXQ) * XPQ + (XPXQ - ZPZQ) * ZPQ t0 = (t0 ** 2) # [(XPZQ + ZPXQ) * XPQ + (XPXQ - ZPZQ) * ZPQ] ^ 2 t1 = (t * PQ[1]) # (XPZQ + ZPXQ) * ZPQ s = (ZPZQ * PQ[0]) # ZPZQ * XPQ t1 = (t1 + s) # (XPZQ + ZPXQ) * ZPQ + ZPZQ * XPQ s = (XPXQ * PQ[0]) # (XPXQ) * XPQ s = (s + s) # 2 * [(XPXQ) * XPQ] s = (s + s) # 4 * [(XPXQ) * XPQ] t1 = (t1 * s) # [(XPZQ + ZPXQ) * ZPQ + ZPZQ * XPQ] * (4 * [(XPXQ) * XPQ]) t = (ZPZQ * PQ[1]) # ZPZQ * ZPQ XPXQ = (t0 - t1) # [(XPZQ + ZPXQ) * XPQ + (XPXQ - ZPZQ) * ZPQ] ^ 2 - [(XPZQ + ZPXQ) * ZPQ + ZPZQ * XPQ] * (4 * [(XPXQ) * XPQ]) ZPZQ = (s * t) # (4 * [(XPXQ) * XPQ]) * (ZPZQ * ZPQ) # --- B1 = (ZPZQ + ZPZQ) # 2C B0 = (XPXQ + B1) # A + 2C B1 = (B1 + B1) # 4C return [B0, B1] 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] def xdbladd(P, Q, PQ, A): """ ---------------------------------------------------------------------- xdbladd() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ, and the projective Montgomery constant (a24 : 1) = (A + 2C: 4C) where E : y^2 = x^3 + (A/C)x^2 + x output: the projective Montgomery x-coordinate point x([2]P), x([P+Q]) ---------------------------------------------------------------------- """ #T = xdbl(P, A) #S = xadd(P, Q, PQ) #return T, S t0 = (P[0] + P[1]) t1 = (P[0] - P[1]) X2 = (t0 ** 2) t2 = (Q[0] - Q[1]) X3 = (Q[0] + Q[1]) t0 = (t0 * t2) Z2 = (t1 ** 2) # --- t1 = (t1 * X3) t2 = (X2 - Z2) X2 = (X2 * Z2) X3 = (A * t2) Z3 = (t0 - t1) Z2 = (X3 + Z2) X3 = (t0 + t1) # --- Z2 = (Z2 * t2) Z3 = (Z3 ** 2) X3 = (X3 ** 2) Z3 = (PQ[0] * Z3) X3 = (PQ[1] * X3) return [X2, Z2], [X3, Z3] def xtpl(P, A): """ ---------------------------------------------------------------------- xtpl() 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([3]P) ---------------------------------------------------------------------- """ # A - 2C C = A[0] - A[1] # --- t0 = (P[0] - P[1]) t2 = (t0 ** 2) t1 = (P[0] + P[1]) t3 = (t1 ** 2) t4 = (t1 + t0) t0 = (t1 - t0) # --- t1 = (t4 ** 2) t1 = (t1 - t3) t1 = (t1 - t2) t5 = (t3 * A[0]) t3 = (t5 * t3) t6 = (t2 * C) # --- t2 = (t2 * t6) t3 = (t2 - t3) t2 = (t5 - t6) t1 = (t2 * t1) t2 = (t3 + t1) t2 = (t2 ** 2) # --- X = (t2 * t4) t1 = (t3 - t1) t1 = (t1 ** 2) Z = (t1 * t0) return [X,Z] 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 Ladder3pt(m, P, Q, PQ, A): """ ---------------------------------------------------------------------- Ladder3pt() input : a projective Montgomery x-coordinate point x(P) := XP/ZP, x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ, the affine Montgomery constant A where E : y^2 = x^3 + Ax^2 + x, and a positive integer m output: the projective Montgomery x-coordinate point x(P + [m]Q) ---------------------------------------------------------------------- """ X0 = list([field(Q[0]), field(Q[1])]) X1 = list([field(P[0]), field(P[1])]) X2 = list([field(PQ[0]), field(PQ[1])]) t = 0x1 for i in range(0, bitlength(p), 1): X1, X2 = cswap(X1, X2, t & m == 0) X0, X1 = xdbladd(X0, X1, X2, A) X1, X2 = cswap(X1, X2, t & m == 0) t <<= 1 return X1 # Golden ration is used in prac algorithm phi = (1.0 + sqrt(0.5)) / 2.0 phi_nu, phi_de = phi.as_integer_ratio() def euclid2d(m, n, P, Q, PQ, A): """ The 2-dimensional scalar pseudomultiplication: x([r]P + [s - r]P) with r = s / {Golden Ratio}') """ s0, s1 = m, n x0 = list(P) x1 = list(Q) diff= list(PQ) while s0 != 0: if s1 < s0: x0, x1 = cswap(x0, x1, 1) s0, s1 = s1, s0 if s1 <= 4*s0: # Fibonacci step x = list(x0) x0 = xadd(x1, x0, diff) diff= list(x) s1 -= s0 elif (s0 % 2) == (s1 % 2): x0 = xadd(x1, x0, diff) x1 = xdbl(x1, A) s1 = (s1 - s0) // 2 elif (s1 % 2) == 0: diff=xadd(x1, diff, x0) x1 =xdbl(x1, A) s1 //= 2 else: diff= xadd(x0, diff, x1) x0 = xdbl(x0, A) s0 //= 2 while s1 % 2 == 0: x1 = xdbl(x1, A) s1 //= 2 if s1 > 1: # Ladder step on the missing part: x0 will correspond with Ladder(x1) diff= list(x1) x0 = xdbl(x1, A) s1_binary = bin(s1)[2:][::-1] s1_length = len(s1_binary) for i in range(s1_length - 2, -1, -1): x0, x1 = cswap(x0, x1, int(s1_binary[i + 1]) ^ int(s1_binary[i])) x1 = xadd(x0, x1, diff) x0 = xdbl(x0, A) x0, x1 = cswap(x0, x1, int(s1_binary[0])) else: # In this case, the output should correspond with x1, thus we swap to x0 x0, x1 = cswap(x0, x1, 1) return x0 def prac(k, P, A): """ PRAC algorithm: (simplified) 1-D Euclidean pseudomultiplication """ s = k infty = [field(1), field(0)] # Point at infinity # Reducing the problem from k = 2^i x s to s x = list(P) while s % 2 == 0: x = xdbl(x, A) s //= 2 r = (s * phi_nu) // phi_de x = euclid2d(r, s - r, x, x, infty, A) return x 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): """ isfullorder() checks if the point at infinity belongs or not to a given list """ tmp = [not isinfinity(seq_i) for seq_i in seq] return reduce(lambda x, y: (x and y), tmp) def generators(A): """ generators() looks for two full-order poinst on E[pi + 1] or E[pi - 1] """ output = [[0, 0], [0, 0]] while [0, 0] in output: T_p, T_m = elligator(A) T_p = prac(cofactor, T_p, A) if isfullorder(cofactor_multiples(T_p, A, range(0, n, 1))) and output[ 0 ] == [0, 0]: output[0] = list(T_p) T_m = prac(cofactor, T_m, A) if isfullorder(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): """ crisscross() computes a*c + b*d, and a*c - b*d """ t_1 = (alpha * delta) t_2 = (beta * gamma) return (t_1 + t_2), (t_1 - t_2) if prime in parameters['csidh'].keys(): def issupersingular(A): """ issupersingular() verifies supersingularity """ while True: T_p, _ = elligator(A) # T_p is always in GF(p), and thus has torsion (p+1) T_p = prac(cofactor, 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 elif prime in parameters['bsidh'].keys(): def issupersingular(A): """ issupersingular() verifies supersingularity """ while True: P, _ = elligator(A) # T_p is always in GF(p²) # Is P a torsion-(p + 1)? T_p = prac(cofactor_p, P, A) Tp = cofactor_multiples(T_p, A, range(0, np, 1)) Tp = [xmul(Tp[i], A, i) for i in range(0, np, 1)] Tp = [isinfinity(Tp_i) for Tp_i in Tp] # Is P a torsion-(p - 1)? T_m = prac(cofactor_m, P, A) Tm = cofactor_multiples(T_m, A, range(np, n, 1)) Tm = [xmul(Tm[i - np], A, i) for i in range(np, n, 1)] Tm = [isinfinity(Tm_i) for Tm_i in Tm] return reduce(lambda x, y: (x and y), Tp) or reduce(lambda x, y: (x and y), Tm) else: assert False, "only CSIDH and B-SIDH are currently implemented" return attrdict(**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 from https://github.com/JJChiDguez/csidh_withstrategies (which generated these keys). 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=[7, -14, -16, -5, 7, -4, 6, -16, 14, 22, -10, 5, 16, 8, -17, -1, -1, -11, 5, 11, 1, -1, 7, -5, 10, -2, 14, -13, 12, -4, -11, 10, -9, -9, 5, 9, 1, 9, 9, 0, 6, -4, 6, -5, 4, -6, 2, 4, 6, 3, 0, -2, 0, -6, -5, -3, 5, 5, -3, 5, 1, -1, 3, -1, 4, -2, 0, 0, 2, 4, 0, 4, 4, 1], pk=[4728146032486912801094583991353286261973546366143541397245686753762901316062339836438041892681193229023769378118684082801187071058443826977747221505877393, 2983658557129372264342771993184706542050709103347982710108512572730239907028070100828017140583214399358736276490324811325106128107134002761476490169757932], compressed=0x3BD2E2F7B2EA49932420DAF81189C350519FF0E29589587C0344111D4822C80AD88100DAC094898DE9FF615A61929B2A6D23C1C149CB031E47AE3BC4E4DC6A19, ), B=attrdict( sk=[3, 14, 18, -15, 17, 22, -12, -14, -20, 10, 10, 3, 10, -8, -17, 13, -15, 19, -19, -21, -15, 23, 11, 1, -2, 10, 6, -3, 0, 6, 9, 12, -9, -9, 3, 5, -1, -9, 7, -2, -8, 8, 0, -3, 4, -2, 0, -2, -6, -7, 0, 2, 6, -4, -5, 3, 5, -5, 1, -3, -5, -1, -1, -3, 2, 4, 0, 0, 4, 2, 2, 4, -2, 3], pk=[3960926043577541377267819439096804391618603251899293401108425870931130716835203327490747655445345747395550952611253698265199934334080097314192061511711009, 487723576446903129146235324971287599085008588633854300660046476997824105838685867154322957184759627700657558727262867917195404714619211343006256912881095], compressed=0x3E533EEDF73446A8FCDAE46A97E4855C62E7A67B6F5CBE2710087C8FFC3C63A4BFA6B365553FB23219955B13A99207721F936E790795CB0AE09D206655197063, ), ) ss = 0x13F022ADE3CC59641C069BF9FE798A6AACE097DF451EBDF95DC016978E494748BD9935F311F4D403AFBC96021FD19EBB7381D077D2A1A8D59FC81F97508B4F5A prime = 'p512' formula = NotImplemented style = 'df' tuned = False exponent = 10 multievaluation = False uninitialized = False verbose = False def setUp(self): self.csidh = CSIDH( 'montgomery', prime=self.prime, formula=self.formula, style=self.style, exponent=self.exponent, tuned=self.tuned, uninitialized=self.uninitialized, multievaluation=self.multievaluation, verbose=self.verbose, ) self.coeff = self.csidh.curve.coeff self.field = self.csidh.field def test_dh_AB(self): self.assertEqual( self.coeff(self.csidh.gae.GAE_at_A(self.keys.A.sk, [self.field(pk) for pk in self.keys.B.pk])).x, self.ss, ) def test_dh_BA(self): self.assertEqual( self.coeff(self.csidh.gae.GAE_at_A(self.keys.B.sk, [self.field(pk) for pk in self.keys.A.pk])).x, self.ss, ) def test_compress(self): for keys in self.keys.values(): self.assertEqual(keys.compressed, self.csidh.curve.coeff([self.field(pk) for pk in keys.pk]).x) 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([self.field(pk) for pk in keys.pk]))).x )