Beispiel #1
0
def main(ctx, **kwargs):
    """

    \b
      ,-~~-.___.          
     / |  '     \\        
    (  )         0        
     \_/-, ,----'         
        ====           // 
       /  \-'~;    /~~~(O)
      /  __/~|   /       |
    =(  _____| (_________|

    """
    algo_args = kwargs.copy()
    algorithm = algo_args.pop('algorithm')
    algo_args.pop('benchmark')
    if algorithm == 'csidh':
        from sidh.csidh import CSIDH

        algo = CSIDH(**algo_args)
    elif algorithm == 'bsidh':
        from sidh.bsidh import BSIDH
        algo_args.pop('style')
        algo_args.pop('exponent')
        algo = BSIDH(**algo_args)
    else:
        click.echo('algorithm not implemented')
        raise Exit(1)
    kwargs['algo'] = algo
    ctx.meta['sidh.kwargs'] = attrdict(kwargs)
Beispiel #2
0
    def __init__(self, curvemodel, prime, formula, tuned, multievaluation,
                 verbose):

        self.params = attrdict(parameters['bsidh'][prime])
        self.prime = prime
        self.tuned = tuned
        self.multievaluation = multievaluation
        self.verbose = verbose

        random = SystemRandom()

        if curvemodel == 'montgomery':
            self.curve = MontgomeryCurve(prime)
            self.A = self.curve.A
            self.fp = self.curve.fp
        else:
            self.curve = None
            raise NotImplemented

        if formula == 'hvelu':
            self.formula = Hvelu(self.curve, self.tuned, self.multievaluation)
        elif formula == 'tvelu':
            self.formula = Tvelu(self.curve)
        elif formula == 'svelu':
            self.formula = Svelu(self.curve, self.tuned, self.multievaluation)
        else:
            self.formula = None
            raise NotImplemented

        if self.formula is not None and self.curve is not None:
            self.gae = Gae(prime, self.tuned, self.curve, self.formula)
        else:
            self.gae = None
            raise NotImplemented
Beispiel #3
0
    def __init__(
        self,
        curvemodel,
        prime,
        formula,
        style,
        tuned,
        exponent,
        multievaluation,
        verbose,
    ):
        self.curvemodel = curvemodel
        self.prime = prime
        self.style = style
        self._exponent = exponent
        self.tuned = tuned
        self.multievaluation = multievaluation
        self.fp = None
        self.params = attrdict(parameters['csidh'][prime])
        self.params.update(self.params[style])

        if self.curvemodel == 'montgomery':
            self.curve = MontgomeryCurve(prime, style)
            self.fp = self.curve.fp
        else:
            self.curve = None
            raise NotImplemented

        if formula == 'hvelu':
            self.formula = Hvelu(self.curve, self.tuned, self.multievaluation)
        elif formula == 'tvelu':
            self.formula = Tvelu(self.curve)
        elif formula == 'svelu':
            self.formula = Svelu(self.curve, self.tuned, self.multievaluation)

        if self.style == 'df':
            self.gae = Gae_df(prime, self.tuned, self.curve, self.formula)
        elif self.style == 'wd1':
            self.gae = Gae_wd1(prime, self.tuned, self.curve, self.formula)
        elif self.style == 'wd2':
            self.gae = Gae_wd2(prime, self.tuned, self.curve, self.formula)
        else:
            self.gae = NotImplemented
Beispiel #4
0
def csidh_parameters(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    A = algo.curve.A
    global_L = L = algo.params.L
    n = algo.params.n
    m = algo.params.m
    delta = algo.params.delta
    full_torsion_points = algo.curve.full_torsion_points
    set_parameters_velu = algo.formula.set_parameters_velu
    set_zero_ops = algo.fp.set_zero_ops
    get_ops = algo.fp.get_ops
    KPs = algo.formula.KPs
    xISOG = algo.formula.xISOG
    xEVAL = algo.formula.xEVAL
    sJ_list = algo.formula.sJ_list
    xMUL = algo.curve.xMUL

    if True:

        # T_p belongs to E[pi - 1]
        # T_m belongs to E[pi + 1]
        T_p, T_m = full_torsion_points(A)

    else:

        # T_m belongs to E[pi - 1]
        # T_p belongs to E[pi + 1]
        T_m, T_p = full_torsion_points(A)

    assert len(L) == n
    parameters = dict()
    for idx in range(0, n, 1):

        # -------------------------------------------------------------
        # Random kernel point
        Tp = list(T_p)
        for i in range(0, n, 1):
            if i != idx:
                Tp = xMUL(Tp, A, i)

        # -------------------------------------------------------------
        # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
        # These paramters are required in KPs, xISOG, and xEVAL
        if global_L[idx] == 3:
            b = 0
            c = 0
        else:
            b = int(floor(sqrt(global_L[idx] - 1) / 2.0))
            c = int(floor((global_L[idx] - 1.0) / (4.0 * b)))

        b += 1
        parameters[str(idx)] = []
        for j in range(0, int(floor(sqrt(pi * (b - 1)) / 1.0)), 1):

            b -= 1
            c = int(floor((global_L[idx] - 1.0) / (4.0 * b)))
            set_parameters_velu(b, c, idx)

            total_cost = [0, 0, 0]
            # -------------------------------------------------------------
            # KPs procedure
            set_zero_ops()
            KPs(Tp, A, idx)
            t = get_ops()
            total_cost[0] += t[0]
            total_cost[1] += t[1]
            total_cost[2] += t[2]

            # -------------------------------------------------------------
            # xISOG
            set_zero_ops()
            B = xISOG(A, idx)
            t = get_ops()
            total_cost[0] += t[0]
            total_cost[1] += t[1]
            total_cost[2] += t[2]

            # -------------------------------------------------------------
            # xEVAL bench
            set_zero_ops()
            Tm = xEVAL(T_m, A)

            t = get_ops()
            total_cost[0] += t[0]
            total_cost[1] += t[1]
            total_cost[2] += t[2]

            # assert(validate(B))
            parameters[str(idx)].append(
                (b, c, t, total_cost[0] + total_cost[1]))

        if global_L[idx] == 3:
            parameters[str(idx)] = (0, 0, None, None)
        else:
            parameters[str(idx)] = min(parameters[str(idx)],
                                       key=lambda tup: tup[3])

        print(parameters[str(idx)][0], parameters[str(idx)][1])
    return attrdict(name='print-parameters', **locals())
Beispiel #5
0
def Gae_wd2(prime, tuned, curve, formula):

    fp = curve.fp
    L = global_L = curve.L
    n = parameters['csidh'][prime]['n']
    m = parameters['csidh'][prime]['wd2']['m']
    temporal_m = list(set(m))
    exponent_of_two = curve.exponent_of_two

    random = SystemRandom()

    # random_key() implements an uniform random sample from [-m_1,m_1] x ... x -[m_n, m_n]
    random_key = lambda m=m: [random.randint(-m_i, m_i) for m_i in m]
    '''
    security()
    inputs : the list M of maximum number of degree-(l_i) isogeny constructions
             to be performed, and the length of M
    output : bits of security that M brings
    '''
    security = lambda M, n: sum(list([log(2 * M[i] + 1, 2) for i in range(n)]))

    # In order to achieve efficiency, the optimal strategies and their cost are saved in two global dictionaries (hash tables)
    S = {1: {}}  # Initialization of each strategy
    C = {1: {}}  # Initialization of the costs: 0.

    for i in range(n):

        S[1][tuple([L[i]])] = []
        # Strategy with a list with only one element (a small odd prime number l_i)
        C[1][tuple([L[i]])] = (
            formula.C_xISOG[i] - curve.C_xMUL[i] +
            2.0 * numpy.array([4.0, 2.0, 6.0])
        )  # For catching the weigth of horizontal edges of the form [(0,j),(0,j+1)]

    for i in range(2, n + 1):

        C[i] = {}
        S[i] = {}
    '''
        dynamic_programming_algorithm():
        inputs: the list of small odd primes to be processed and its length
        output: the optimal strategy and its cost of the input list of small odd primes
    '''
    def pubkey(sk):
        C_out, L_out, R_out, S_out, r_out = strategy_block_cost(
            L[::-1], m[::-1])
        if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                      (0 in temporal_m)):
            return GAE(
                curve.A,
                sk,
                [L_out[0]],
                [R_out[0]],
                [S_out[0]],
                [temporal_m[-1]],
                m,
            )
        else:
            return GAE(curve.A, sk, L_out, R_out, S_out, r_out, m)

    def dh(sk, pk):
        assert curve.validate(pk), "public key does not validate"
        C_out, L_out, R_out, S_out, r_out = strategy_block_cost(
            L[::-1], m[::-1])
        if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                      (0 in temporal_m)):
            ss = GAE(
                pk,
                sk,
                [L_out[0]],
                [R_out[0]],
                [S_out[0]],
                [temporal_m[-1]],
                m,
            )
        else:
            ss = GAE(pk, sk, L_out, R_out, S_out, r_out, m)
        return ss

    def dynamic_programming_algorithm(L, n):
        nonlocal S, C
        # If the approach uses dummy operations, to set DUMMY = 2.0;
        # otherwise, to set DUMMY = 1.0 (dummy free approach);

        if len(L) != n:

            # If the list of prime numbers doesn't have size n, then we return [],-1
            print(
                "error:\tthe list of prime numbers has different size from %d."
                % n)
            return [], -1
        else:

            # Assuming #L = n, we proceed.
            get_neighboring_sets = lambda L, k: [
                tuple(L[i:i + k]) for i in range(n - k + 1)
            ]  # This function computes all the k-tuple: (l_1, l_2, ..., l_{k)),
            # (l_2, l_3, ..., l_{k+1)), ..., (l_{n-k}, l_{n-k+1, ..., l_{n)).
            for i in range(2, n + 1):

                for Tuple in get_neighboring_sets(L, i):

                    if C[i].get(Tuple) is None:

                        alpha = [
                            (
                                b,
                                C[len(Tuple[:b])][Tuple[:b]] +
                                C[  # Subtriangle on the right side with b leaves
                                    len(Tuple[b:])][Tuple[b:]] +
                                2.0  # Subtriangle on the left side with (i - b) leaves
                                * sum([
                                    curve.C_xMUL[global_L.index(t)]
                                    for t in Tuple[:b]
                                ]) +
                                2.0  # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with b leaves
                                * sum([
                                    formula.C_xEVAL[global_L.index(t)]
                                    for t in Tuple[b:]
                                ]) +
                                2.0  # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves
                                * sum([
                                    curve.C_xMUL[global_L.index(t)]
                                    for t in Tuple[b:]
                                ]),  # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves
                            ) for b in range(1, i - 1)
                        ] + [(
                            i - 1,
                            C[i - 1][Tuple[:(i - 1)]] +
                            C[  # Subtriangle on the right side with (i - 1) leaves
                                1][Tuple[(i - 1):]] +
                            1.0  # Subtriangle on the left side with 1 leaf (only one vertex)
                            * sum([
                                curve.C_xMUL[global_L.index(t)]
                                for t in Tuple[:(i - 1)]
                            ]) +
                            2.0  # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with 1 leaf
                            * formula.C_xEVAL[global_L.index(Tuple[i - 1])] +
                            2.0  # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - 1) leaves
                            * curve.C_xMUL[global_L.index(
                                Tuple[i - 1]
                            )],  # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - 1) leaves
                        )]
                        b, C[i][Tuple] = min(
                            alpha, key=lambda t: curve.measure(t[1])
                        )  # We save the minimal cost corresponding to the triangle with leaves Tuple
                        S[i][Tuple] = (
                            [b] + S[i - b][Tuple[b:]] + S[b][Tuple[:b]]
                        )  # We save the optimal strategy corresponding to the triangle with leaves Tuple

            return (
                S[n][tuple(L)],
                C[n][tuple(L)] + curve.C_xMUL[global_L.index(L[0])] -
                2.0 * numpy.array([4.0, 2.0, 6.0]),
            )  # The weight of the horizontal edges [(0,n-1),(0,n)] must be equal to C_xISOG[global_L.index(L[0])].

    '''
        evaluate_strategy():
        inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x,
                 a list of two projective Montgomery x-coordinate torsion-(l_1 x ... l_n) points x(T_+) := XP/ZP
                 and x(T_-) := XM/ZM, the list of small odd primes [l_1, l_2, ..., l_n], an strategy and length
                 of the given list of small odd primes, maximum number of degree-l_i isogeny constructions, and
                 the secret integer vector to be evaluated
        output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x
                 is E / <P>, the new maximum and current number of degree-l_i isogeny constructions to be performed
                 after the strategy evaluation.
        
        NOTE: T_+ belongs to E[pi - 1] and T_- belongs to E[pi + 1]. In particular, P = [T_-, T_+]
    '''

    def evaluate_strategy(E, P, L, strategy, n, m, e):

        v = list(m)
        u = list(e)
        ramifications = []
        moves = [
            0
        ]  # moves: this list determines whether an isogeny construction must be performed
        k = 0  # k: current element of the strategy

        ramifications.append(
            list(P)
        )  # list of pair of points (T_-, T_+) such that T_- in E[\pi + 1] and T_+ in E[\pi - 1]
        E_i = list(E)
        for i in range(len(strategy)):

            pos = global_L.index(
                L[n - 1 - i])  # Current element of global_L to be required

            # Reaching the vertex (n - 1 - i, i)

            # Vertical edges
            prev = sum(moves)
            while (prev + strategy[k]) < (n - 1 - i):

                moves.append(
                    strategy[k])  # Number of vertical edges to be performed
                T = list(
                    [list(ramifications[-1][0]),
                     list(ramifications[-1][1])])
                for j in range(prev, prev + strategy[k], 1):
                    T = list([
                        curve.xMUL(T[0], E_i, global_L.index(L[j])),
                        curve.xMUL(T[1], E_i, global_L.index(L[j])),
                    ])

                ramifications.append(list([list(T[0]), list(T[1])]))
                prev += strategy[k]
                k += 1

            # Vertical edges without ramifications
            s_i = sign(e[pos])  # Sign of e[pos]
            c_i = (s_i + 1) // 2  # Constant-swap of T_+ and T_-
            if prev < (n - 1 - i):

                moves.append(
                    strategy[k]
                )  # Number of vertical edges (without ramifications) to be performed
                T = list(
                    [list(ramifications[-1][0]),
                     list(ramifications[-1][1])])

                T[0][0], T[1][0] = fp.fp_cswap(T[0][0], T[1][0], c_i)
                T[0][1], T[1][1] = fp.fp_cswap(T[0][1], T[1][1], c_i)
                for j in range(prev, prev + strategy[k], 1):
                    T[0] = list(curve.xMUL(T[0], E_i, global_L.index(
                        L[j])))  # A single scalar multiplication is required

                T[0][0], T[1][0] = fp.fp_cswap(T[0][0], T[1][0], c_i)
                T[0][1], T[1][1] = fp.fp_cswap(T[0][1], T[1][1], c_i)

                ramifications.append(list([list(T[0]), list(T[1])]))
                prev += strategy[k]
                k += 1

            # At this point, vertex (n - 1 - i, i) has been reached
            if (v[pos] > 0
                ):  # Maximum number of degree-l_{pos} isogeny constructions?

                # At this step, ramifications[-1] is the i-th leaf
                # Swap the torsion-(l_{n-j}) elliptic curve points on the current leaf
                #                           T_- <- ramifications[-1][0]
                #                           T_+ <- ramifications[-1][1]
                ramifications[-1][0][0], ramifications[-1][1][0] = fp.fp_cswap(
                    ramifications[-1][0][0], ramifications[-1][1][0],
                    c_i)  # XT_+ <-> XT_-
                ramifications[-1][0][1], ramifications[-1][1][1] = fp.fp_cswap(
                    ramifications[-1][0][1], ramifications[-1][1][1],
                    c_i)  # ZT_+ <-> ZT_-

                if curve.isinfinity(ramifications[-1][0]) == False:

                    # T_+ or T_- ?
                    # Root
                    # Swap the torsion elliptic curve points on the current root
                    (
                        ramifications[0][0][0],
                        ramifications[0][1][0],
                    ) = fp.fp_cswap(ramifications[0][0][0],
                                    ramifications[0][1][0], c_i)
                    (
                        ramifications[0][0][1],
                        ramifications[0][1][1],
                    ) = fp.fp_cswap(ramifications[0][0][1],
                                    ramifications[0][1][1], c_i)

                    # Dummy or NOT Dummy degree-(l_{n-1-i}) isogeny construction, that's the question?
                    b_i = isequal[u[pos] == 0]

                    (
                        ramifications[-1][0][0],
                        ramifications[0][0][0],
                    ) = fp.fp_cswap(ramifications[-1][0][0],
                                    ramifications[0][0][0], b_i)
                    (
                        ramifications[-1][0][1],
                        ramifications[0][0][1],
                    ) = fp.fp_cswap(ramifications[-1][0][1],
                                    ramifications[0][0][1], b_i)

                    if formula.name != 'tvelu':
                        # This branchs corresponds with the use of the new velu's formulaes

                        if tuned:
                            formula.set_parameters_velu(
                                formula.sJ_list[pos], formula.sI_list[pos],
                                pos)

                        else:
                            # -------------------------------------------------------------
                            # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                            # These paramters are required in KPs, xISOG, and formula.xEVAL
                            if global_L[pos] == 3:
                                b = 0
                                c = 0
                            else:
                                b = int(floor(sqrt(global_L[pos] - 1) / 2.0))
                                c = int(
                                    floor((global_L[pos] - 1.0) / (4.0 * b)))

                            formula.set_parameters_velu(b, c, pos)

                        if (formula.name == 'hvelu'
                                and global_L[pos] <= formula.HYBRID_BOUND):
                            K = formula.KPs(ramifications[-1][0], E_i, pos)

                        else:
                            formula.KPs(ramifications[-1][0], E_i, pos)
                            T = curve.xMUL(ramifications[-1][0], E_i, pos)

                    else:
                        K = formula.KPs(ramifications[-1][0], E_i, pos)

                    (
                        ramifications[-1][0][0],
                        ramifications[0][0][0],
                    ) = fp.fp_cswap(ramifications[-1][0][0],
                                    ramifications[0][0][0], b_i)
                    (
                        ramifications[-1][0][1],
                        ramifications[0][0][1],
                    ) = fp.fp_cswap(ramifications[-1][0][1],
                                    ramifications[0][0][1], b_i)

                    # New isogeny construction
                    C_i = formula.xISOG(E_i, pos)

                    # Next, the horizontal edge [(0,i),(0,i+1)] is performed
                    if formula.name == 'tvelu' or (
                            formula.name == 'hvelu'
                            and global_L[pos] <= formula.HYBRID_BOUND):
                        d_i = (global_L[pos] - 1) // 2
                        mask = isequal[(global_L[pos] == 3
                                        )]  # catching special case when l = 3

                        Z = formula.yADD(K[(d_i + mask) - 1], K[0],
                                         K[(d_i + mask) -
                                           2])  # y([d_i + 1]K[0])
                        Z[0], K[d_i][0] = fp.fp_cswap(Z[0], K[d_i][0],
                                                      mask ^ 1)
                        Z[1], K[d_i][1] = fp.fp_cswap(Z[1], K[d_i][1],
                                                      mask ^ 1)

                        T = formula.yADD(
                            K[d_i], K[d_i - 1],
                            K[0])  # y([2*d_i + 1]K[0]) := y([l_i]K[0])
                        T = [
                            fp.fp_add(T[1], T[0]),
                            fp.fp_sub(T[1], T[0]),
                        ]  # x([l_i]K[0])

                    ramifications[0][1] = curve.xMUL(ramifications[0][1], E_i,
                                                     pos)
                    T_prime = list(ramifications[0][1])

                    if formula.name == 'tvelu' or (
                            formula.name == 'hvelu'
                            and global_L[pos] <= formula.HYBRID_BOUND):
                        ramifications[0][0] = formula.xEVAL(
                            ramifications[0][0], pos)
                        ramifications[0][1] = formula.xEVAL(
                            ramifications[0][1], pos)
                    else:
                        ramifications[0][0] = formula.xEVAL(
                            ramifications[0][0], E_i)
                        ramifications[0][1] = formula.xEVAL(
                            ramifications[0][1], E_i)

                    T[0], ramifications[0][0][0] = fp.fp_cswap(
                        T[0], ramifications[0][0][0], b_i)
                    T[1], ramifications[0][0][1] = fp.fp_cswap(
                        T[1], ramifications[0][0][1], b_i)
                    T_prime[0], ramifications[0][1][0] = fp.fp_cswap(
                        T_prime[0], ramifications[0][1][0], b_i)
                    T_prime[1], ramifications[0][1][1] = fp.fp_cswap(
                        T_prime[1], ramifications[0][1][1], b_i)

                    (
                        ramifications[0][0][0],
                        ramifications[0][1][0],
                    ) = fp.fp_cswap(ramifications[0][0][0],
                                    ramifications[0][1][0], c_i)
                    (
                        ramifications[0][0][1],
                        ramifications[0][1][1],
                    ) = fp.fp_cswap(ramifications[0][0][1],
                                    ramifications[0][1][1], c_i)

                    # The remainder horizontal edges are performed
                    for j in range(1, len(moves) - 1, 1):

                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)

                        # T_- (or T_)
                        T = curve.xMUL(ramifications[j][0], E_i, pos)

                        if formula.name == 'tvelu' or (
                                formula.name == 'hvelu'
                                and global_L[pos] <= formula.HYBRID_BOUND):
                            ramifications[j][0] = formula.xEVAL(
                                ramifications[j][0], pos)
                        else:
                            ramifications[j][0] = formula.xEVAL(
                                ramifications[j][0], E_i)

                        T[0], ramifications[j][0][0] = fp.fp_cswap(
                            T[0], ramifications[j][0][0], b_i)
                        T[1], ramifications[j][0][1] = fp.fp_cswap(
                            T[1], ramifications[j][0][1], b_i)

                        # T_+ or (T_+)
                        ramifications[j][1] = curve.xMUL(
                            ramifications[j][1], E_i, pos)

                        T = list(ramifications[j][1])
                        if formula.name == 'tvelu' or (
                                formula.name == 'hvelu'
                                and global_L[pos] <= formula.HYBRID_BOUND):
                            ramifications[j][1] = formula.xEVAL(
                                ramifications[j][1], pos)
                        else:
                            ramifications[j][1] = formula.xEVAL(
                                ramifications[j][1], E_i)

                        T[0], ramifications[j][1][0] = fp.fp_cswap(
                            T[0], ramifications[j][1][0], b_i)
                        T[1], ramifications[j][1][1] = fp.fp_cswap(
                            T[1], ramifications[j][1][1], b_i)

                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)

                    C_i[0], E_i[0] = fp.fp_cswap(C_i[0], E_i[0], b_i ^ 1)
                    C_i[1], E_i[1] = fp.fp_cswap(C_i[1], E_i[1], b_i ^ 1)

                    v[pos] -= 1
                    u[pos] -= s_i * (b_i ^ 1)

                else:

                    for j in range(0, len(moves) - 1, 1):

                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)
                        ramifications[j][1] = curve.xMUL(
                            ramifications[j][1], E_i, pos)
                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)
            else:
                # This branch only depends on randomness

                # At this step, ramifications[-1] is the i-th leaf
                ramifications[-1][0][0], ramifications[-1][1][0] = fp.fp_cswap(
                    ramifications[-1][0][0], ramifications[-1][1][0], c_i)
                ramifications[-1][0][1], ramifications[-1][1][1] = fp.fp_cswap(
                    ramifications[-1][0][1], ramifications[-1][1][1], c_i)

                if curve.isinfinity(ramifications[-1][0]) == False:

                    for j in range(0, len(moves) - 1, 1):

                        ramifications[j][0] = curve.xMUL(
                            ramifications[j][0], E_i, pos)
                        ramifications[j][1] = curve.xMUL(
                            ramifications[j][1], E_i, pos)
                else:

                    for j in range(0, len(moves) - 1, 1):

                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)
                        ramifications[j][1] = curve.xMUL(
                            ramifications[j][1], E_i, pos)
                        (
                            ramifications[j][0][0],
                            ramifications[j][1][0],
                        ) = fp.fp_cswap(ramifications[j][0][0],
                                        ramifications[j][1][0], c_i)
                        (
                            ramifications[j][0][1],
                            ramifications[j][1][1],
                        ) = fp.fp_cswap(ramifications[j][0][1],
                                        ramifications[j][1][1], c_i)

            moves.pop()
            ramifications.pop()

        pos = global_L.index(
            L[0])  # Current element of global_L to be required
        s_i = sign(e[pos])  # Sign of e[pos]
        c_i = (s_i + 1) // 2  # Constant-swap of T_+ and T_-

        # T_+ or T_- ?
        # Root
        ramifications[0][0][0], ramifications[0][1][0] = fp.fp_cswap(
            ramifications[0][0][0], ramifications[0][1][0], c_i)
        ramifications[0][0][1], ramifications[0][1][1] = fp.fp_cswap(
            ramifications[0][0][1], ramifications[0][1][1], c_i)

        if curve.isinfinity(ramifications[0][0]) == False:

            if m[pos] > 0:

                b_i = isequal[e[pos] == 0]

                if formula.name != 'tvelu':
                    # This branchs corresponds with the use of the new velu's formulaes

                    if tuned:
                        formula.set_parameters_velu(formula.sJ_list[pos],
                                                    formula.sI_list[pos], pos)

                    else:
                        # -------------------------------------------------------------
                        # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                        # These paramters are required in KPs, xISOG, and formula.xEVAL
                        if global_L[pos] == 3:
                            b = 0
                            c = 0
                        else:
                            b = int(floor(sqrt(global_L[pos] - 1) / 2.0))
                            c = int(floor((global_L[pos] - 1.0) / (4.0 * b)))

                        formula.set_parameters_velu(b, c, pos)
                    formula.KPs(ramifications[0][0], E_i, pos)

                else:
                    formula.KPs(ramifications[0][0], E_i, pos)

                C_i = formula.xISOG(E_i, pos)
                C_i[0], E_i[0] = fp.fp_cswap(C_i[0], E_i[0], b_i ^ 1)
                C_i[1], E_i[1] = fp.fp_cswap(C_i[1], E_i[1], b_i ^ 1)

                v[pos] -= 1
                u[pos] -= s_i * (b_i ^ 1)

        return E_i, v, u

    '''
        geometric_serie()
        inputs: and integer m, and a prime number l
        output: the nearest integer to
                      l
                m x -----
                    l - 1
    '''

    def geometric_serie(m, l):

        l_float = float(l)
        m_float = float(m)
        return floor((m_float * l_float) / (l_float - 1.0) + 0.5)

    '''
        filtered()
        inputs : a list L and a sublist SL of L
        output : L \\ SL
    '''
    filtered = lambda List, sublist: [e for e in List if e not in sublist]
    '''
        rounds()
        inputs : an integer vector (maximum number of isogeny constructions to be performed), 
                 and the length of the vector
        output : the subset of (indexes of) the small odd primes that determines the optimal 
                 strategy to be used, the number of times that each strategy will be used, and
                 the complement of each subset (with respect to the set of all the small odd primes)
    '''

    def rounds(e, n):
        tmp_N = range(n)
        tmp_e = list(e)
        rounds_out = []
        sublists_L = []
        sublists_C = []
        while [e_i for e_i in tmp_e if e_i > 0] != []:
            e_min = min([e_i for e_i in tmp_e if e_i > 0])
            rounds_out.append(e_min)
            sublists_L.append([i for i in tmp_N if tmp_e[i] >= e_min])
            sublists_C.append(filtered(tmp_N, sublists_L[len(sublists_L) - 1]))
            tmp_e = [(tmp_e[i] - e_min) for i in tmp_N]

        return rounds_out, sublists_L, sublists_C

    '''
        GAE():
        inputs : a projective Montgomery constants A24:= A + 2C and C24:=4C where E : y^2 = x^3 + (A/C)*x^2 + x,
                 the secret integer vector to be evaluated, all the sublist of small odd primes to be required and 
                 their complement of each sublist, the (optimal) strategies corresponding to each sublist and their 
                 number of times to be evaluated, and the maximum number of degree-l_i isogeny constructions
        output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x
                 corresponds with the image of the group action evaluation.

        NOTE: GAE comes from Group Action Evaluation, and the input sublists are determined by the output of function
              rounds().
              THIS IS THE IMPLEMENTATION OF OUR PROPOSED STRATEGY METHOD.
    '''

    def GAE(A, e, L, R, St, r, m):

        E_k = list(A)
        n = len(L)

        for j in range(0, n, 1):

            for k in range(0, r[j], 1):

                T_p, T_m = curve.elligator(E_k)

                for ii in range(0, exponent_of_two, 1):
                    T_p = curve.xDBL(T_p, E_k)
                    T_m = curve.xDBL(T_m, E_k)

                for l in R[j]:
                    T_p = curve.xMUL(T_p, E_k, global_L.index(l))
                    T_m = curve.xMUL(T_m, E_k, global_L.index(l))

                E_k, m, e = evaluate_strategy(
                    E_k,
                    list([list(T_m), list(T_p)]),
                    L[j],
                    St[j],
                    len(L[j]),
                    m,
                    e,
                )

        # Multiplicative strategy on the set of unreached small odd prime numbers
        unreached_sop = [global_L[i] for i in range(len(global_L)) if m[i] > 0]
        remainder_sop = [l for l in global_L if l not in unreached_sop]

        while len(unreached_sop) > 0:

            T_p, T_m = curve.elligator(E_k)

            for ii in range(0, exponent_of_two, 1):
                T_p = curve.xDBL(T_p, E_k)
                T_m = curve.xDBL(T_m, E_k)

            for l in remainder_sop:
                T_p = curve.xMUL(T_p, E_k, global_L.index(l))
                T_m = curve.xMUL(T_m, E_k, global_L.index(l))

            current_n = len(unreached_sop)
            E_k, m, e = evaluate_strategy(
                E_k,
                list([list(T_m), list(T_p)]),
                unreached_sop,
                list(range(current_n - 1, 0, -1)),
                current_n,
                m,
                e,
            )

            # If the maximum of degree-(l_k) has been reached then the current batch (and its complement) must be updated
            tmp_unreached = [
                unreached_sop[k] for k in range(current_n)
                if m[global_L.index(unreached_sop[k])] > 0
            ]
            tmp_remainder = [
                unreached_sop[k] for k in range(current_n)
                if m[global_L.index(unreached_sop[k])] == 0
            ]

            unreached_sop = list(
                tmp_unreached)  # Removing elements from the batch
            remainder_sop = (
                remainder_sop + tmp_remainder
            )  # Adding elements to the complement of the batch

        return E_k

    ######################################################################################################################
    # Next functions are used for computing optimal bounds
    basis = numpy.eye(n, dtype=int)

    # Next function computes the expected cost of our approach by assuming we have full torsion points
    def strategy_block_cost(L, e):

        elligator_cost = numpy.array([7.0, 3.0, 10.0])  # Elligator cost
        mul_fp_by_four = (numpy.array([4.0, 2.0, 4.0]) * exponent_of_two
                          )  # Cost of computing x([2^exponent_of_two]P)

        n = len(L)
        e_prime = [geometric_serie(e[k], L[k]) for k in range(n)]

        tmp_r, tmp_Ls, tmp_Cs = rounds(e_prime, n)

        C_e = numpy.array([0.0, 0.0, 0.0])
        S_out = []
        L_out = []
        R_out = []
        for j in range(len(tmp_r)):

            R_out.append([L[k] for k in tmp_Cs[j]])
            L_out.append([L[k] for k in tmp_Ls[j]])

            bo_C = 2.0 * sum(
                [curve.C_xMUL[global_L.index(L[k])] for k in tmp_Cs[j]])
            S_tmp, go_C = dynamic_programming_algorithm(
                [L[k] for k in tmp_Ls[j]], len(tmp_Ls[j]))

            S_out.append(S_tmp)
            C_e = (C_e +
                   (go_C + bo_C + elligator_cost + 2.0 * mul_fp_by_four) *
                   tmp_r[j])

        return C_e, L_out, R_out, S_out, tmp_r

    return attrdict(name='wd2', **locals())
Beispiel #6
0
def MontgomeryCurve(prime):
    A = parameters['bsidh']['A']
    nm = parameters['bsidh'][prime]['nm']
    np = parameters['bsidh'][prime]['np']
    Lp = parameters['bsidh'][prime]['Lp']
    Lm = parameters['bsidh'][prime]['Lm']
    Ep = parameters['bsidh'][prime]['Ep']
    Em = parameters['bsidh'][prime]['Em']
    p = parameters['bsidh'][prime]['p']

    fp = F_p2(p)

    SQR = 1.00
    # In F_p, we have SQR_{F_p} = SQR x MUL_{F_p}
    ADD = 0.00
    # In F_p, we have ADD_{F_p} = ADD x MUL_{F_p}
    measure = lambda x: (x[0] + SQR * x[1] + ADD * x[2])

    def dacs(l, r0, r1, r2, chain):
        '''
        dacs()
        inputs: a small odd prime number l, three integer numbers, and a list
        output: all the differential additions chains corresponding with the input l

        NOTE: this is a recursive approach
        '''
        if r2 == l:

            return [(chain, r2)]
        elif r2 < l and len(chain) <= 1.5 * log(l, 2):

            return dacs(l, r0, r2, r2 + r0, chain + [1]) + dacs(
                l, r1, r2, r2 + r1, chain + [0])
        else:
            return []

    def sdac(l):
        '''
        sdac()
        input: a small odd prime number l
        output: the shortest differential additions chains corresponding with the input l

        NOTE: this function uses a recursive function
        '''
        all_dacs = dacs(l, 1, 2, 3, [])
        return min(all_dacs, key=lambda t: len(t[0]))[0]

    path = resource_filename('sidh', "data/sdacs/" + prime)
    SDACS = filename_to_list_of_lists_of_ints(path)
    if len(SDACS) == 0:
        print("// SDAC's to be computed")
        SDACS = list(
            map(sdac, Lp)
        )  # Shortest Differential Addition Chains for each small odd prime l in Lp
        # Next nm SDACS
        SDACS = SDACS + list(
            map(sdac, Lm)
        )  # Shortest Differential Addition Chains for each small odd prime l in Lm
        #print("// Storing SDAC's in a file")
        #write_list_of_lists_of_ints_to_file(path, SDACS)
    SDACS_LENGTH = list(map(len, SDACS))

    ##   if sys.argv[0] != 'header.py':
    ##       print("// Shortest Differential Addition Chains (SDAC) for each l_i;")
    #try:

    #    # List of Small odd primes, L := [l_0, ..., l_{n-1}]
    #    f = open(sdacs_data + setting.prime)
    #    if sys.argv[0] != 'header.py':
    #        print("// SDAC's to be read from a file")

    #    SDACS = []
    #    SDACS_LENGTH = []
    #    for i in range(0, np + nm, 1):

    #        tmp = f.readline()
    #        tmp = [int(b) for b in tmp.split()]
    #        SDACS.append(tmp)
    #        SDACS_LENGTH.append(len(tmp))

    #    f.close()

    #except IOError:

    #    if sys.argv[0] != 'header.py':
    #        print("// SDAC's to be computed")

    #    # First np SDACS
    #    SDACS = list(
    #        map(sdac, Lp)
    #    )  # Shortest Differential Addition Chains for each small odd prime l in Lp
    #    SDACS_LENGTH = [len(sdacs) for sdacs in SDACS]
    #    # Next nm SDACS
    #    SDACS = SDACS + list(
    #        map(sdac, Lm)
    #    )  # Shortest Differential Addition Chains for each small odd prime l in Lm
    #    SDACS_LENGTH = [len(sdacs) for sdacs in SDACS]

    #    # Storing the SDACS to be used
    #    print("// Storing SDAC's in a file")
    #    f = open(sdacs_data + setting.prime, 'w')
    #    for i in range(0, np + nm, 1):
    #        f.writelines(' '.join([str(tmp) for tmp in SDACS[i]]) + '\n')

    #    f.close()

    # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to include case l=2 and l=4]
    SIDp = reduce(lambda x, y: x + y,
                  [[Lp[i]] * Ep[i] for i in range(0, np, 1)])
    # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}]
    SIDm = reduce(lambda x, y: x + y,
                  [[Lm[i]] * Em[i] for i in range(0, nm, 1)])

    SID = list(SIDp + SIDm)
    n = len(SID)

    L = global_L = list(Lp + Lm)

    cMUL = lambda l: numpy.array([
        4.0 * (SDACS_LENGTH[global_L.index(l)] + 2),
        2.0 * (SDACS_LENGTH[global_L.index(l)] + 2),
        6.0 * (SDACS_LENGTH[global_L.index(l)] + 2) - 2.0,
    ])
    C_xMUL = list(map(cMUL, global_L))  # list of the costs of each [l]P

    def coeff(A):
        '''
        ----------------------------------------------------------------------
        coeff()
        input : projective Montgomery constants A24 := A + 2C and C24 := 4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the affine Montgomery coefficient A/C
        ----------------------------------------------------------------------
        '''
        output = fp.fp2_add(A[0], A[0])  # (2 * A24)
        output = fp.fp2_sub(output, A[1])  # (2 * A24) - C24
        C24_inv = fp.fp2_inv(A[1])  # 1 / (C24)
        output = fp.fp2_add(output, output)  # 4*A = 2[(2 * A24) - C24]
        output = fp.fp2_mul(output, C24_inv)  # A/C = 2[(2 * A24) - C24] / C24
        return output

    def jinvariant(A):
        '''
        ----------------------------------------------------------------------
        jinvariant() input : projective Montgomery constants A24 := A + 2C and C24
        := 4C where E : y^2 = x^3 + (A/C)*x^2 + x output: the j-invariant of E
        ----------------------------------------------------------------------
        '''
        A4_squared = fp.fp2_add(A[0], A[0])  # (2 * A24)
        A4_squared = fp.fp2_sub(A4_squared, A[1])  # (2 * A24) - C24
        A4_squared = fp.fp2_add(A4_squared,
                                A4_squared)  # 4*A = 2[(2 * A24) - C24]

        # Now, we have A = A' / C' := A4 / A[1] = (4*A) / (4*C)
        A4_squared = fp.fp2_sqr(A4_squared)  # (A')^2
        C4_squared = fp.fp2_sqr(A[1])  # (C')^2
        t = fp.fp2_add(C4_squared, C4_squared)  # 2 * [(C')^2]

        num = fp.fp2_add(C4_squared, t)  # 3 * [(C')^2]
        num = fp.fp2_sub(A4_squared, num)  # (A')^2 - 3 * [(C')^2]
        s = fp.fp2_sqr(num)  # { (A')^2 - 3 * [(C')^2] }^2
        num = fp.fp2_mul(num, s)  # { (A')^2 - 3 * [(C')^2] }^3

        C4_squared = fp.fp2_sqr(C4_squared)  # (C')^4
        den = fp.fp2_add(t, t)  # 4 * [(C')^2]
        den = fp.fp2_sub(A4_squared, den)  # (A')^2 - 4 * [(C')^2]
        den = fp.fp2_mul(den,
                         C4_squared)  # {(A')^2 - 4 * [(C')^2] } * [(C')^4]
        den = fp.fp2_inv(den)  # 1 / {(A')^2 - 4 * [(C')^2] } * [(C')^4]

        num = fp.fp2_mul(
            num, den
        )  # j := { (A')^2 - 3 * [(C')^2] }^3 / {(A')^2 - 4 * [(C')^2] } * [(C')^4]
        num = fp.fp2_add(num, num)  #   2*j
        num = fp.fp2_add(num, num)  #   4*j
        num = fp.fp2_add(num, num)  #   8*j
        num = fp.fp2_add(num, num)  #  16*j
        num = fp.fp2_add(num, num)  #  32*j
        num = fp.fp2_add(num, num)  #  64*j
        num = fp.fp2_add(num, num)  # 128*j
        num = fp.fp2_add(num, num)  # 256*j
        return num

    def isinfinity(P):
        """
        isinfinity(P) determines if x(P) := (XP : ZP) = (1 : 0)
        """
        return (P[1][0] == 0) and (P[1][1] == 0)

    def areequal(P, Q):
        """
        areequal(P, Q) determines if x(P) = x(Q)
        """
        XPZQ = fp.fp2_mul(P[0], Q[1])
        ZPXQ = fp.fp2_mul(P[1], Q[0])
        return (XPZQ[0] == ZPXQ[0]) and (XPZQ[0] == ZPXQ[0])

    def xDBL(P, A):
        '''
        ----------------------------------------------------------------------
        xDBL()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, and
                the  projective Montgomery constants A24:= A + 2C and C24:=4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the projective Montgomery x-coordinate point x([2]P)
        ----------------------------------------------------------------------
        '''
        t_0 = fp.fp2_sub(P[0], P[1])
        t_1 = fp.fp2_add(P[0], P[1])
        t_0 = fp.fp2_sqr(t_0)
        t_1 = fp.fp2_sqr(t_1)
        Z = fp.fp2_mul(A[1], t_0)
        X = fp.fp2_mul(Z, t_1)
        t_1 = fp.fp2_sub(t_1, t_0)
        t_0 = fp.fp2_mul(A[0], t_1)
        Z = fp.fp2_add(Z, t_0)
        Z = fp.fp2_mul(Z, t_1)
        return [X, Z]

    def xADD(P, Q, PQ):
        '''
        ----------------------------------------------------------------------
        xADD()
        input : the projective Montgomery x-coordinate points x(P) := XP/ZP,
                x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ
        output: the projective Montgomery x-coordinate point x(P+Q)
        ----------------------------------------------------------------------
        '''
        a = fp.fp2_add(P[0], P[1])
        b = fp.fp2_sub(P[0], P[1])
        c = fp.fp2_add(Q[0], Q[1])
        d = fp.fp2_sub(Q[0], Q[1])
        a = fp.fp2_mul(a, d)
        b = fp.fp2_mul(b, c)
        c = fp.fp2_add(a, b)
        d = fp.fp2_sub(a, b)
        c = fp.fp2_sqr(c)
        d = fp.fp2_sqr(d)
        X = fp.fp2_mul(PQ[1], c)
        Z = fp.fp2_mul(PQ[0], d)
        return [X, Z]

    def xDBLADD(P, Q, PQ, A):
        """
        Next function computes x([2]P) and x(P + Q)
        """

        # In C-code implementations this can be optimized
        S = xADD(P, Q, PQ)
        T = xDBL(P, A)
        return T, S

    # Modificar esta parte para usar cadenas de addicion
    def xMUL(P, A, j):
        '''
        ----------------------------------------------------------------------
        xMUL()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the
                projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, and an positive integer j
        output: the projective Montgomery x-coordinate point x([L[j]]P)
        ----------------------------------------------------------------------
        '''

        P2 = xDBL(P, A)
        R = [P, P2, xADD(P2, P, P)]

        for i in range(SDACS_LENGTH[j] - 1, -1, -1):

            T = xADD(R[2], R[SDACS[j][i] ^ 1], R[SDACS[j][i]])

            R[0] = list(R[SDACS[j][i] ^ 1])
            R[1] = list(R[2])
            R[2] = list(T)

        return R[2]

    def Ladder3pt(m, P, Q, PQ, A):
        """
        Next function computes x(P + [m]Q)
        """
        X0 = list([list(Q[0]), list(Q[1])])
        X1 = list([list(P[0]), list(P[1])])
        X2 = list([list(PQ[0]), list(PQ[1])])
        t = 0x1
        for i in range(0, bitlength(p), 1):
            # In C-code implementations this branch should be implemented with cswap's
            if t & m != 0:
                X0, X1 = xDBLADD(X0, X1, X2, A)
            else:
                X0, X2 = xDBLADD(X0, X2, X1, A)
            t <<= 1
        return X1

    def prime_factors(P, A, points):
        """
        ----------------------------------------------------------------------
        prime_factors()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the
                projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, and subset of |[0, n]|
        output: the projective Montgomery x-coordinate points x([(p+1) / l_0]P),
                x([(p+1) / l_1]P), ..., x([(p+1) / l_{n-1}]P).
        ----------------------------------------------------------------------
        """
        n = len(points)
        if n == 1:
            # In this recursion level we have an order-l point
            return [P]
        elif n > 0:
            # We proceed by applying a divide-and-conquer procedure
            h = n // 2
            if h > 0:

                # 1st half
                first_half = []
                second_P = P
                for j in range(h):
                    second_P = xMUL(second_P, A, points[j])
                    first_half.append(points[j])
                # 2nd half
                second_half = []
                first_P = P
                for j in range(h, n):
                    first_P = xMUL(first_P, A, points[j])
                    second_half.append(points[j])

                return prime_factors(first_P, A, first_half) + prime_factors(
                    second_P, A, second_half)
            return []

    def random_affine_point(A):
        """
        Random affine point (x,y) in E : y^2 = x^3 + Ax^2 + x
        """
        return "Not implemented yet!"

    def difference_point(P, Q, A):
        """
        Next function computes x(P - Q) giving two affine points P and Q
        """
        return "Not implemented yet!"

    def isfull_order(seq):
        tmp = [not isinfinity(seq_i) for seq_i in seq]
        return reduce(lambda x, y: (x and y), tmp)

    def full_torsion_points(A):
        return "Not implemented yet!"

    def CrissCross(alpha, beta, gamma, delta):
        t_1 = fp.fp2_mul(alpha, delta)
        t_2 = fp.fp2_mul(beta, gamma)
        return fp.fp2_add(t_1, t_2), fp.fp2_sub(t_1, t_2)

    return attrdict(**locals())
Beispiel #7
0
def bsidh_test(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    p = algo.params.p
    np = algo.params.np
    Ep = algo.params.Ep
    nm = algo.params.nm
    Em = algo.params.Em
    global_L = algo.curve.L
    curve = algo.curve
    formula = algo.formula
    coeff = curve.coeff
    random = SystemRandom()

    print("p := 0x%X;" % p)
    print("fp := GF(p);")
    print("_<x> := PolynomialRing(fp);")
    print("fp2<i> := ext<fp | x^2 + 1>;")
    print("Pr<x> := PolynomialRing(fp2);")

    # Reading public generators points
    f = open(resource_filename('sidh', 'data/gen/' + setting.prime))

    # x(PA), x(QA) and x(PA - QA)
    PQA = f.readline()
    PQA = [int(x, 16) for x in PQA.split()]
    PA = [list(PQA[0:2]), [0x1, 0x0]]
    QA = [list(PQA[2:4]), [0x1, 0x0]]
    PQA = [list(PQA[4:6]), [0x1, 0x0]]

    # x(PB), x(QB) and x(PB - QB)
    PQB = f.readline()
    PQB = [int(x, 16) for x in PQB.split()]
    PB = [list(PQB[0:2]), [0x1, 0x0]]
    QB = [list(PQB[2:4]), [0x1, 0x0]]
    PQB = [list(PQB[4:6]), [0x1, 0x0]]

    f.close()

    A = algo.A
    a = coeff(A)

    print("E := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" %
          (a[0], a[1]))

    S = [list(PA[0]), list(PA[1])]
    T = [list(QA[0]), list(QA[1])]
    ST = [list(PQA[0]), list(PQA[1])]

    for i in range(0, np, 1):
        for idx in range(0, Ep[i], 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    assert curve.isinfinity(S)
    assert curve.isinfinity(T)
    assert curve.isinfinity(ST)

    print("\n// Verifying torsion-(p + 1) points")
    print("// x([p + 1]PA) = (1:0)?\t", curve.isinfinity(S))
    print("// x([p + 1]QA) = (1:0)?\t", curve.isinfinity(T))
    print("// x([p + 1]PQA) = (1:0)?\t", curve.isinfinity(ST))

    S = [list(PB[0]), list(PB[1])]
    T = [list(QB[0]), list(QB[1])]
    ST = [list(PQB[0]), list(PQB[1])]

    for i in range(np, np + nm, 1):
        for idx in range(0, Em[i - np], 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    assert curve.isinfinity(S)
    assert curve.isinfinity(T)
    assert curve.isinfinity(ST)
    print("\n// Verifying torsion-(p - 1) points")
    print("// x([p - 1]PB) = (1:0)?\t", curve.isinfinity(S))
    print("// x([p - 1]QB) = (1:0)?\t", curve.isinfinity(T))
    print("// x([p - 1]PQB) = (1:0)?\t", curve.isinfinity(ST))

    # Case (p + 1)
    S = [list(PA[0]), list(PA[1])]
    T = [list(QA[0]), list(QA[1])]
    ST = [list(PQA[0]), list(PQA[1])]

    assert curve.isinfinity(S) == False
    assert curve.isinfinity(T) == False
    assert curve.isinfinity(ST) == False

    for i in range(0, np, 1):
        for idx in range(0, Ep[i] - 1, 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    print("\n// Verifying orders")
    assert curve.isfull_order(curve.prime_factors(S, A, range(0, np, 1)))
    assert curve.isfull_order(curve.prime_factors(T, A, range(0, np, 1)))
    assert curve.isfull_order(curve.prime_factors(ST, A, range(0, np, 1)))
    print(
        "// PA is a full order point?\t",
        curve.isfull_order(curve.prime_factors(S, A, range(0, np, 1))),
    )
    print(
        "// QA is a full order point?\t",
        curve.isfull_order(curve.prime_factors(T, A, range(0, np, 1))),
    )
    print(
        "// QPA is a full order point?\t",
        curve.isfull_order(curve.prime_factors(ST, A, range(0, np, 1))),
    )

    # Case (p - 1)
    S = [list(PB[0]), list(PB[1])]
    T = [list(QB[0]), list(QB[1])]
    ST = [list(PQB[0]), list(PQB[1])]

    assert curve.isinfinity(S) == False
    assert curve.isinfinity(T) == False
    assert curve.isinfinity(ST) == False

    for i in range(np, np + nm, 1):
        for idx in range(0, Em[i - np] - 1, 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    print("\n// Verifying orders")
    assert curve.isfull_order(curve.prime_factors(S, A, range(np, np + nm, 1)))
    assert curve.isfull_order(curve.prime_factors(T, A, range(np, np + nm, 1)))
    assert curve.isfull_order(curve.prime_factors(ST, A, range(np, np + nm,
                                                               1)))
    print(
        "// PB is a full order point?\t",
        curve.isfull_order(curve.prime_factors(S, A, range(np, np + nm, 1))),
    )
    print(
        "// QB is a full order point?\t",
        curve.isfull_order(curve.prime_factors(T, A, range(np, np + nm, 1))),
    )
    print(
        "// QPB is a full order point?\t",
        curve.isfull_order(curve.prime_factors(ST, A, range(np, np + nm, 1))),
    )

    # Three point ladder: case (p + 1)
    S = [list(PA[0]), list(PA[1])]
    T = [list(QA[0]), list(QA[1])]
    ST = [list(PQA[0]), list(PQA[1])]

    assert curve.isinfinity(S) == False
    assert curve.isinfinity(T) == False

    for i in range(0, np, 1):
        for idx in range(0, Ep[i] - 1, 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    k = random.randint(0, p)
    R = curve.Ladder3pt(k, S, T, ST, A)
    # print("k := 0x%X;" % k)
    # print("boolR, R := IsPoint(E, (0x%X + i * 0x%X) / (0x%X + i * 0x%X));" %
    # (R[0][0], R[0][1], R[1][0], R[1][1]))

    T_p = [list(R[0]), list(R[1])]
    T_m = [list(S[0]), list(S[1])]
    print(
        "\n// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment, and using torsion-(p + 1) points"
    )
    for idx in range(0, np, 1):

        # -------------------------------------------------------------
        # Random kernel point
        Tp = list(T_p)
        for i in range(idx + 1, np, 1):
            Tp = curve.xMUL(Tp, A, i)

        print("// l:\t%7d |" % global_L[idx], end="")
        total_cost = [0, 0, 0]

        if setting.formula != 'tvelu':

            if setting.tuned:
                formula.set_parameters_velu(formula.sJ_list[idx],
                                            formula.sI_list[idx], idx)

            else:
                # -------------------------------------------------------------
                # Parameters sJ and sI correspond with the parameters b and b'
                # from example 4.12 of https://eprint.iacr.org/2020/341 These
                # paramters are required in KPs, xISOG, and xEVAL
                if global_L[idx] <= 4:
                    b = 0
                    c = 0
                else:
                    b = int(floor(sqrt(global_L[idx] - 1) / 2.0))
                    c = int(floor((global_L[idx] - 1.0) / (4.0 * b)))

                formula.set_parameters_velu(b, c, idx)

            formula.print_parameters_velu()

        # -------------------------------------------------------------
        # KPs procedure
        formula.fp.fp.set_zero_ops()
        formula.KPs(Tp, A, idx)
        formula.fp.fp.show_ops("Kps", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xISOG
        formula.fp.fp.set_zero_ops()
        Tp[0], A[0] = formula.fp.fp2_cswap(Tp[0], A[0], global_L[idx] == 4)
        Tp[1], A[1] = formula.fp.fp2_cswap(Tp[1], A[1], global_L[idx] == 4)
        B = formula.xISOG(A, idx)
        Tp[0], A[0] = formula.fp.fp2_cswap(Tp[0], A[0], global_L[idx] == 4)
        Tp[1], A[1] = formula.fp.fp2_cswap(Tp[1], A[1], global_L[idx] == 4)
        formula.fp.fp.show_ops("xISOG", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xEVAL: kernel point determined by the next isogeny evaluation
        formula.fp.fp.set_zero_ops()
        if (setting.formula == 'tvelu'
                or (setting.formula == 'hvelu'
                    and global_L[idx] <= formula.HYBRID_BOUND)
                or (global_L[idx] == 4)):
            T_p = formula.xEVAL(T_p, idx)
        else:
            T_p = formula.xEVAL(T_p, A)

        # xEVAL bench
        formula.fp.fp.set_zero_ops()
        if (setting.formula == 'tvelu'
                or (setting.formula == 'hvelu'
                    and global_L[idx] <= formula.HYBRID_BOUND)
                or (global_L[idx] == 4)):
            T_m = formula.xEVAL(T_m, idx)
        else:
            T_m = formula.xEVAL(T_m, A)

        formula.fp.fp.show_ops("xEVAL", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]
        print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ")
        print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) /
                                   (global_L[idx] + 2.0)))

        # assert(validate(B))
        A = list(B)

        # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))
        # print("assert(Random(B) * (p + 1) eq B!0);")
        # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1]))
        # print("assert(BOOL);")

    print(
        "\n// All the l_i's have been processed, output of xISOG corresponds with the given below"
    )
    # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))
    a = coeff(A)
    print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" %
          (a[0], a[1]))
    print("assert(Random(B) * (p + 1) eq B!0);")

    A = [[0x8, 0x0], [0x4, 0x0]]
    # Three point ladder: case (p - 1)
    S = [list(PB[0]), list(PB[1])]
    T = [list(QB[0]), list(QB[1])]
    ST = [list(PQB[0]), list(PQB[1])]

    assert curve.isinfinity(S) == False
    assert curve.isinfinity(T) == False

    for i in range(np, np + nm, 1):
        for idx in range(0, Em[i - np] - 1, 1):
            S = curve.xMUL(S, A, i)
            T = curve.xMUL(T, A, i)
            ST = curve.xMUL(ST, A, i)

    k = random.randint(0, p)
    R = curve.Ladder3pt(k, S, T, ST, A)
    # print("k := 0x%X;" % k)
    # print("boolR, R := IsPoint(E, (0x%X + i * 0x%X) / (0x%X + i * 0x%X));" %
    # (R[0][0], R[0][1], R[1][0], R[1][1]))
    T_p = [list(R[0]), list(R[1])]
    T_m = [list(S[0]), list(S[1])]
    print(
        "\n// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment, and using torsion-(p - 1) points"
    )
    for idx in range(np, np + nm, 1):

        # -------------------------------------------------------------
        # Random kernel point
        Tp = list(T_p)
        for i in range(idx + 1, np + nm, 1):
            Tp = curve.xMUL(Tp, A, i)

        print("// l:\t%7d |" % global_L[idx], end="")
        total_cost = [0, 0, 0]

        if setting.formula != 'tvelu':

            if setting.tuned:
                formula.set_parameters_velu(formula.sJ_list[idx],
                                            formula.sI_list[idx], idx)

            else:
                # -------------------------------------------------------------
                # Parameters sJ and sI correspond with the parameters b and b'
                # from example 4.12 of https://eprint.iacr.org/2020/341 These
                # paramters are required in KPs, xISOG, and xEVAL
                if global_L[idx] == 3:
                    b = 0
                    c = 0
                else:
                    b = int(floor(sqrt(global_L[idx] - 1) / 2.0))
                    c = int(floor((global_L[idx] - 1.0) / (4.0 * b)))

                formula.set_parameters_velu(b, c, idx)

            formula.print_parameters_velu()

        # -------------------------------------------------------------
        # KPs procedure
        formula.fp.fp.set_zero_ops()
        formula.KPs(Tp, A, idx)
        formula.fp.fp.show_ops("Kps", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xISOG
        formula.fp.fp.set_zero_ops()
        B = formula.xISOG(A, idx)
        formula.fp.fp.show_ops("xISOG", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xEVAL: kernel point determined by the next isogeny evaluation
        formula.fp.fp.set_zero_ops()
        if setting.formula == 'tvelu' or (
                setting.formula == 'hvelu'
                and global_L[idx] <= formula.HYBRID_BOUND):
            T_p = formula.xEVAL(T_p, idx)
        else:
            T_p = formula.xEVAL(T_p, A)

        # xEVAL bench
        formula.fp.fp.set_zero_ops()
        if setting.formula == 'tvelu' or (
                setting.formula == 'hvelu'
                and global_L[idx] <= formula.HYBRID_BOUND):
            T_m = formula.xEVAL(T_m, idx)
        else:
            T_m = formula.xEVAL(T_m, A)

        formula.fp.fp.show_ops("xEVAL", 1.0, 0.0, False)
        t = formula.fp.fp.get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]
        print("|| cost: %8d" % (total_cost[0] + total_cost[1]), end=" ")
        print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) /
                                   (global_L[idx] + 2.0)))

        # assert(validate(B))
        A = list(B)

        # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))
        # print("assert(Random(B) * (p + 1) eq B!0);")
        # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1]))
        # print("assert(BOOL);")

    print(
        "\n// All the l_i's have been processed, output of xISOG corresponds with the given below"
    )
    a = coeff(A)
    print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" %
          (a[0], a[1]))
    print("assert(Random(B) * (p + 1) eq B!0);")
    # """

    print(
        "\n\"If no errors were showed using magma calculator, then all experiments were successful passed!\";"
    )
    print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n")
    """
    from bsidh.poly_redc import *
    flen = random.randint(1, 32)
    glen = random.randint(1, 32)
    f = [ [random.randint(0, p - 1), random.randint(0, p - 1)]  for i in range(0, flen, 1) ]
    g = [ [random.randint(0, p - 1), random.randint(0, p - 1)]  for i in range(0, glen, 1) ]
    fg = poly_mul(f, flen, g, glen)
    print("f := ", f, ";")
    print("g := ", g, ";")
    print("fg := ", fg, ";")
    print("Pr![ fp2!fi : fi in f] * Pr![ fp2!gi : gi in g] eq Pr![ fp2!fgi : fgi in fg];");
    """
    return attrdict(name='bsidh-test', **locals())
Beispiel #8
0
def csidh_header(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    L = algo.params.L
    n = algo.params.n
    m = algo.params.m
    delta = algo.params.delta
    geometric_serie = algo.gae.geometric_serie
    rounds = algo.gae.rounds

    strategy_block_cost = algo.gae.strategy_block_cost
    basis = numpy.eye(n, dtype=int)
    measure = algo.curve.measure

    # ==========================================================================

    if len(set(m)) > 1:
        # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i)
        LABEL_m = 'different_bounds'
    else:
        # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i)
        LABEL_m = 'with_same_bounds'

    if setting.tuned:
        verb = '-suitable'
    else:
        verb = '-classical'

    try:

        # List of Small Odd Primes, L := [l_0, ..., l_{n-1}]
        m_prime = [geometric_serie(m[k], L[k]) for k in range(n)]
        r_out, L_out, R_out = rounds(m_prime[::-1], n)
        for j in range(0, len(r_out), 1):

            R_out[j] = list([L[::-1][k] for k in R_out[j]])
            L_out[j] = list([L[::-1][k] for k in L_out[j]])

        f = open(strategy_data + setting.algorithm + '-' + setting.prime +
                 '-' + setting.style + '-' + setting.formula + '-' + LABEL_m +
                 verb)
        # print("// Strategies to be read from a file")
        S_out = []
        for i in range(0, len(r_out), 1):

            tmp = f.readline()
            tmp = [int(b) for b in tmp.split()]
            S_out.append(tmp)

        f.close()

    except IOError:

        # print("// Strategies to be computed")
        C_out, L_out, R_out, S_out, r_out = strategy_block_cost(
            L[::-1], m[::-1])
        f = open(
            strategy_data + setting.algorithm + '-' + setting.prime + '-' +
            setting.style + '-' + setting.formula + '-' + LABEL_m + verb,
            'w',
        )
        for i in range(0, len(r_out)):

            f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n')

        f.close()

    if (len(set(m)) == 1) or ((len(set(m)) == 2) and (0 in set(m))):
        L_out = list([L_out[0]])
        R_out = list([R_out[0]])
        S_out = list([S_out[0]])
        r_out = list([r_out[0]])
    # ---
    k = 3  # Number of rows (format of the list)
    # ---

    print("#ifndef _STRATEGIES_H_")
    print("#define _STRATEGIES_H_\n")
    print(
        "// This script assumes the C-code implementation has the list of Small Odd Primes (SOPs) stored such that l_0 < l_1 < ... < l_{n-1}"
    )
    print("// Recall, the strategies process from small SOPs to large SOPs.\n")

    for i in range(len(r_out)):
        print(
            "// -----------------------------------------------------------------------------------------------------------------------------------"
        )
        print("// Strategy number %d\n" % (i))
        L_string = "static uint32_t L%d[] " % (i)
        R_string = "static uint32_t W%d[] " % (i)
        S_string = "static uint32_t S%d[] " % (i)

        printl(
            L_string,
            [L.index(l_i) for l_i in L_out[i]],
            len(L_out[i]) // k + 1,
        )
        if R_out[i] != []:
            printl(
                R_string,
                [L.index(r_i) for r_i in R_out[i]],
                len(R_out[i]) // k + 1,
            )
        else:
            print("static uint32_t W%d[1];" % i)
        if S_out[i] != []:
            printl(S_string, S_out[i], len(S_out[i]) // k + 1)
        else:
            print("static uint32_t S%d[1];" % i)

    print("\n")
    print(
        "// -----------------------------------------------------------------------------------------------------------------------------------"
    )
    print(
        "// -----------------------------------------------------------------------------------------------------------------------------------"
    )
    print("#define NUMBER_OF_DIFFERENT_STRATEGIES  %d" % len(L_out))
    print("")
    L_string = (
        "static uint32_t *L_STRATEGY[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t")
    R_string = (
        "static uint32_t *W_STRATEGY[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t")
    S_string = "static uint32_t *S[NUMBER_OF_DIFFERENT_STRATEGIES] = {\n\t"

    tmp_sizes = "static uint32_t NUMBER_OF_PRIMES[] = {\n\t"
    tmp_round = "static uint8_t ROUNDS[] = {\n\t"

    for i in range(len(L_out) - 1):

        L_string = L_string + "L%d, " % (i)
        R_string = R_string + "W%d, " % (i)
        S_string = S_string + "S%d, " % (i)

        tmp_sizes = tmp_sizes + "%3d," % (len(L_out[i]))
        tmp_round = tmp_round + "%3d," % (r_out[i])

    L_string = L_string + "L%d\n\t};" % (len(L_out) - 1)
    R_string = R_string + "W%d\n\t};" % (len(L_out) - 1)
    S_string = S_string + "S%d\n\t};" % (len(L_out) - 1)

    tmp_sizes = tmp_sizes + "%3d\n\t};" % (len(L_out[len(L_out) - 1]))
    tmp_round = tmp_round + "%3d\n\t};" % (r_out[len(L_out) - 1])

    print(
        "// L_STRATEGY[i] determines the small odd primes l_i per each strategy"
    )
    print(L_string)
    print("\n// W_STRATEGY[i] determines L \ L_STRATEGY[i]")
    print(R_string)
    print(
        "\n// S_STRATEGY[i] determines the optimal strategy for L_STRATEGY[i]")
    print(S_string)

    print("\n// Number of primes for each strategy")
    print(tmp_sizes)
    print("")
    print("// Number of rounds per each different strategy")
    print(tmp_round)

    print("")
    print("// Maximum number of degree-(l_i) isogeny constructions")
    printl("static uint8_t M[]", m, n // k + 1)

    STYLE_NAME = {
        'wd2': 'OAYT-style',
        'wd1': 'MCR-style',
        'df': 'dummy-free-style',
    }[setting.style]
    print(
        "\n#endif /* required framework for the strategies to be used in CSIDH-%s using %s */"
        % (setting.prime[1:], STYLE_NAME))
    return attrdict(name='header', **locals())
Beispiel #9
0
def Poly_mul(curve):
    fp = curve.fp

    # ------------------------------------------------------------------------
    # Table of 2 raised to a negative power
    inverse_of_two = fp.fp2_inv([2, 0])
    max_exp = 2 * int(floor(sqrt(curve.L[-1])))
    negative_powers_of_two = dict()
    negative_powers_of_two[1] = [1, 0]

    j = 1
    j_next = 1
    for i in range(0, max_exp, 1):
        j_next *= 2
        negative_powers_of_two[j_next] = fp.fp2_mul(negative_powers_of_two[j],
                                                    inverse_of_two)
        j = j_next
    # -------------------------------------------------------------------------

    # Here, a polynomial is represented as a list of integers.
    def print_poly(h, lenh):

        # Just a pretty way for printing polynomials
        print(sum([h[i] * (x**i) for i in range(0, lenh, 1)]), ";")
        return None

    # Karatsuba style multiplication of two polynomials
    def karatsuba_mul(f, flen, g, glen):

        if flen < glen:
            return poly_mul(g, glen, f, flen)

        # At this step, we ensure flen >= glen
        if [] == g or [] == f:

            # Multiplication
            return []

        if glen == 1:

            # Multipication by a constant
            return [fp.fp2_mul(f[i], g[0]) for i in range(0, flen, 1)]

        # At this step, we ensure flen >= glen >= 2
        if flen == 2:

            # Multiplication of linear polynomials over fp
            # Thus c(x) = (a * b)(x) is a quadratic polynomial
            c = list([[0, 0], [0, 0], [0, 0]])

            c[0] = fp.fp2_mul(f[0], g[0])  # coeff of x^0
            c[2] = fp.fp2_mul(f[1], g[1])  # coeff of x^2
            f01 = fp.fp2_add(f[0], f[1])
            g01 = fp.fp2_add(g[0], g[1])
            c[1] = fp.fp2_mul(f01, g01)
            c[1] = fp.fp2_sub(c[1], c[0])
            c[1] = fp.fp2_sub(c[1], c[2])  # coeff of x^1
            return c

        if flen == 3:

            # f(x) is a quadratic polynomial
            if glen == 2:

                # g(x) is a linear polynomial
                c = list([[0, 0], [0, 0], [0, 0],
                          [0,
                           0]])  # Thus c(x) = (f * g)(x) is a cubic polynomial

                c[0] = fp.fp2_mul(f[0], g[0])  # coeff of x^0
                c[2] = fp.fp2_mul(f[1], g[1])
                f01 = fp.fp2_add(f[0], f[1])
                g01 = fp.fp2_add(g[0], g[1])
                c[1] = fp.fp2_mul(f01, g01)
                c[1] = fp.fp2_sub(c[1], c[0])
                c[1] = fp.fp2_sub(c[1], c[2])  # coeff of x^1
                c[3] = fp.fp2_mul(f[2], g[1])  # coeff of x^3
                f2g0 = fp.fp2_mul(f[2], g[0])
                c[2] = fp.fp2_add(c[2], f2g0)  # coeff of x^2
                return c

            if glen == 3:

                # g(x) is a a quadratic polynomial

                karatsuba_0 = karatsuba_mul(f[:1], 1, g[:1],
                                            1)  # field multiplication
                karatsuba_1 = karatsuba_mul(
                    f[1:flen], 2, g[1:glen],
                    2)  # multiplication of two linear polynomials

                # Middle part
                f_01 = fp.fp2_add(f[0], f[1])
                f_02 = fp.fp2_add(f[0], f[2])
                g_01 = fp.fp2_add(g[0], g[1])
                g_02 = fp.fp2_add(g[0], g[2])

                t_01 = fp.fp2_mul(f_01, g_01)
                t_02 = fp.fp2_mul(f_02, g_02)

                l_coeff = fp.fp2_sub(t_01, karatsuba_0[0])
                l_coeff = fp.fp2_sub(l_coeff, karatsuba_1[0])
                q_coeff = fp.fp2_sub(t_02, karatsuba_0[0])
                q_coeff = fp.fp2_sub(q_coeff, karatsuba_1[2])
                q_coeff = fp.fp2_add(q_coeff, karatsuba_1[0])

                return list(karatsuba_0 + [l_coeff, q_coeff] + karatsuba_1[1:])

        else:

            # Multiplication of polynomials of degree >= 3 over fp
            nf = flen // 2
            mf = flen - nf
            f_low = f[:nf]
            f_high = f[nf:flen]

            if glen <= nf:

                c0 = karatsuba_mul(f_low, nf, g[:glen], glen)
                c1 = karatsuba_mul(f_high, mf, g[:glen], glen)
                return (c0[:nf] + [
                    fp.fp2_add(c0[nf + i], c1[i])
                    for i in range(0, glen - 1, 1)
                ] + c1[(glen - 1):])

            mg = glen - nf
            g_low = g[:nf]
            g_high = g[nf:glen]

            f_mid = [fp.fp2_add(f_low[i], f_high[i])
                     for i in range(0, nf, 1)] + f_high[nf:]

            fg_low = karatsuba_mul(f_low, nf, g_low, nf)
            fg_high = karatsuba_mul(f_high, mf, g_high, mg)

            if mg < mf:

                g_mid = [
                    fp.fp2_add(g_low[i], g_high[i]) for i in range(0, mg, 1)
                ] + g_low[mg:]
                fg_mid = poly_mul(f_mid, mf, g_mid, nf)

            else:

                g_mid = [
                    fp.fp2_add(g_low[i], g_high[i]) for i in range(0, nf, 1)
                ] + g_high[nf:]
                fg_mid = poly_mul(f_mid, mf, g_mid, mg)

            fg_mid = [
                fp.fp2_sub(fg_mid[i], fg_high[i])
                for i in range(0, mf + mg - 1, 1)
            ] + fg_mid[(mf + mg - 1):]
            fg_mid = [
                fp.fp2_sub(fg_mid[i], fg_low[i])
                for i in range(0, 2 * nf - 1, 1)
            ] + fg_mid[(2 * nf - 1):]

            return (fg_low[:nf] + [
                fp.fp2_add(fg_low[nf + i], fg_mid[i])
                for i in range(0, nf - 1, 1)
            ] + [fg_mid[nf - 1]] + [
                fp.fp2_add(fg_mid[nf + i], fg_high[i])
                for i in range(0, mf - 1)
            ] + fg_high[(mf - 1):])

    def qring_mul(f, g, e):

        n = len(f)
        m = len(f[0])

        if (n == 1) and (m <= 8):

            # The product f[1] * g[1] is directly computed in B = fp[x] / (x^m + 1)
            h = karatsuba_mul(f[0], m, g[0], m)  # Polynomial multiplication

            # Next, we reduce h modulo (x^m + 1)
            h_0 = h[:m]
            h_1 = h[m:]
            h_1 = h_1 + [[0, 0]] * (len(h_0) - len(h_1))

            return [[fp.fp2_sub(h_0[i], h_1[i]) for i in range(0, m, 1)]]

        elif (n == 1) and (m > 8):

            # We need to  embed f[1] and g[1] in B[y]/(y^n2+1) being
            # B = Fp[x]/(x^m2+1) where n2, andm2 are computed as follows
            s = int(floor(log(m, 2))) // 2
            m2 = 2**(s + 1)
            n2 = (2 * m) // m2
            hm2 = m2 // 2  # notice that n2*hm2 = m

            # Now, F and G will corresponds with the  images of f[1] and g[1],
            # respectively, in B2[y]/(y^n2+1); here, B2 = Fp[x]/(x^hm2+1).
            # Notice B2[y]/(y^n2+1) is 'contained' in B[y]/(y^n2+1)
            F = []
            G = []
            ind = -1

            for j in range(0, n2, 1):

                Fj = []
                Gj = []

                for i in range(0, hm2, 1):
                    ind += 1
                    Fj.append(f[0][ind])
                    Gj.append(g[0][ind])

                for i in range(0, hm2, 1):
                    Fj.append([0, 0])
                    Gj.append([0, 0])

                F.append(Fj)
                G.append(Gj)

            # Next, we recursively multiply F and G but now in B[y]/(y^n2+1) = B[y]/(y^n2-x^m2)
            H = qring_mul(F, G, m2)
            # At this point, H is equal to  n2*F*G, so we need to divide by n2
            # in2 =  fp.fp2_inv(n2)   # This inverse can be omited (we only care about polynomials with the same roots)

            # H lives in B[y]/(y^n2+1), 'contained' in Fp[x,y]/(y^n2+1), so to recover
            # the corresponding element h in Fp[x][x^m+1], in H we substitute y by x^hm2.
            h = list([[0, 0]] * m)
            degy = 0
            fp.fp2_op = {0: fp.fp2_add, 1: fp.fp2_sub}
            for j in range(0, n2, 1):

                deg = degy
                for i in range(0, m2, 1):

                    # for a polynomial position (l,k) (that is position [j-1,i-1] when viewed
                    # as sequences), deg is the degree of x^k*y^l after substitution, namely,
                    # deg = l*hm2 + k. Sometimes we have deg>m-1, this is why case we have to
                    # take residues (deg mod m) and also flip the sign of H(l,k) when deg>m-1
                    q, r = divmod(deg, m)
                    # (-1)^q determines if fp.fp2_add or fp.fp2_sub will be needed
                    hr = H[j][i]
                    h[r] = fp.fp2_op[q % 2](h[r], hr)
                    deg += 1

                degy += hm2

            # return [ [fp.fp2_mul(h[i], in2) for i in range(0, m, 1)] ]
            return [[
                fp.fp2_mul(h[i], negative_powers_of_two[n2])
                for i in range(0, m, 1)
            ]]
            # return [list(h)]

        else:

            # The strategy proceeds by spliting the degree-n problem over B=Fp[x]/(x^m+1) into
            # two degree-n/2 over B, and then each of the two problems is handled recursively
            # until reaching degree-0 products over B

            # However, f*g is to be computed in B[y]/(y^n-x^e) = B[y]/[(y^n2)^2-(x^e2)^2]
            n2 = n // 2
            e2 = e // 2

            F1, F2 = [], []
            G1, G2 = [], []
            fp.fp2_op = {
                0: fp.fp2_add,
                1: fp.fp2_sub,
            }  # (-1)^q determines if fp.fp2_add or fp.fp2_sub will be needed
            for j in range(0, n2, 1):

                Fj1, Fj2 = [], []
                Gj1, Gj2 = [], []
                for i in range(0, m, 1):

                    q, r = divmod(i - e2, m)
                    sgn = q % 2
                    # ---
                    Fj1.append(fp.fp2_op[sgn](f[j][i], f[n2 + j][r]))
                    Gj1.append(fp.fp2_op[sgn](g[j][i], g[n2 + j][r]))
                    # ---
                    Fj2.append(fp.fp2_op[1 - sgn](f[j][i], f[n2 + j][r]))
                    Gj2.append(fp.fp2_op[1 - sgn](g[j][i], g[n2 + j][r]))

                F1.append(Fj1)
                F2.append(Fj2)
                G1.append(Gj1)
                G2.append(Gj2)

            # Next, we recursively multiply F1 and G1, and also F2 and G2
            H1 = qring_mul(F1, G1, e2)
            H2 = qring_mul(F2, G2, m + e2)

            h1, h2 = [], []
            for j in range(0, n2, 1):

                h1j, h2j = [], []
                for i in range(0, m, 1):

                    q, r = divmod(i - m + e2, m)
                    sgn = q % 2
                    h1j.append(fp.fp2_add(H1[j][i], H2[j][i]))
                    h2j.append(fp.fp2_op[1 - sgn]([0, 0],
                                                  fp.fp2_sub(
                                                      H1[j][r], H2[j][r])))

                h1.append(h1j)
                h2.append(h2j)

            return h1 + h2

    def poly_mul(f, flen, g, glen):

        ff = list(f) + [[0, 0]] * (glen - flen)
        gg = list(g) + [[0, 0]] * (flen - glen)
        # assert(len(ff) == len(gg))
        hlen = len(ff)

        # hlen > 0 implies `fast` multiplication in fp[x]/(x^n + 1) will be always used
        if hlen > 512:
            n = 2**len(bin(2 * hlen - 1)[2:])
            fg_qring = qring_mul([ff + [[0, 0]] * (n - hlen)],
                                 [gg + [[0, 0]] * (n - hlen)], 0)

            # return [ fp.fp2_mul(fg_qring[0][i], in2) for i in range(0, flen + glen, 1) ]
            return fg_qring[0][:(flen + glen - 1)]

        else:
            # This branch is only for checking if karatsuba is better(?)
            return karatsuba_mul(f, flen, g, glen)

    # Next function computes f*g mod x^n but using the same idea from the C-code implementation of https://eprint.iacr.org/2020/341
    def poly_mul_modxn(n, f, flen, g, glen):

        if flen < glen:
            return poly_mul_modxn(n, g, glen, f, flen)

        # ------------------------------------
        # At this step, we ensure flen >= glen

        # We require f mod x^n and g mod x^n
        if glen > n:
            return poly_mul_modxn(n, f[:n], n, g[:n], n)

        if flen > n:
            return poly_mul_modxn(n, f[:n], n, g, glen)

        if n == 0:
            # This case return zero as the empty list, that is [0]*0
            return []

        if [] == g or [] == f:

            # Multiplication by cero
            return [[0, 0]] * n

        if n == 1:
            # Only the constant coefficients are required
            return [fp.fp2_mul(f[0], g[0])]

        if n >= (flen + glen - 1):

            # Karatsuba multiplication with no possible savings
            return poly_mul(f, flen, g,
                            glen) + [[0, 0]] * (n - flen - glen + 1)

        # At this step we ensure n <  (flen + glen - 1)
        if glen == 1:

            return [fp.fp2_mul(f[i], g[0]) for i in range(0, n, 1)]

        # Here, flen >= glen >= 2
        if n == 2:

            # Multiplication of two linear polynomials modulo x^2
            # And thus, the cuadratic coefficient is not required
            f0g0 = fp.fp2_mul(f[0], g[0])
            f0g1 = fp.fp2_mul(f[0], g[1])
            f1g0 = fp.fp2_mul(f[1], g[0])
            return [f0g0, fp.fp2_add(f0g1, f1g0)]

        if n == 3:

            if glen == 2:

                # Multiplication modulo x^3 of a linear polynomial with another of degree at least 2
                f0g0 = fp.fp2_mul(f[0], g[0])
                f1g1 = fp.fp2_mul(f[1], g[1])

                f01 = fp.fp2_add(f[0], f[1])
                g01 = fp.fp2_add(g[0], g[1])
                t01 = fp.fp2_mul(f01, g01)
                t01 = fp.fp2_sub(t01, f0g0)
                t01 = fp.fp2_sub(t01, f1g1)

                f2g0 = fp.fp2_mul(f[2], g[0])
                return [f0g0, t01, fp.fp2_add(f1g1, f2g0)]

            if glen == 3:
                c00 = fp.fp2_mul(f[0], g[0])  # coeff of x^0

                c11 = fp.fp2_mul(f[1], g[1])
                f01 = fp.fp2_add(f[0], f[1])
                g01 = fp.fp2_add(g[0], g[1])
                c01 = fp.fp2_mul(f01, g01)
                c01 = fp.fp2_sub(c01, c00)
                c01 = fp.fp2_sub(c01, c11)

                f02 = fp.fp2_add(f[0], f[2])
                g02 = fp.fp2_add(g[0], g[2])
                c02 = fp.fp2_mul(f02, g02)
                c22 = fp.fp2_mul(f[2], g[2])
                c02 = fp.fp2_sub(c02, c00)
                c02 = fp.fp2_sub(c02, c22)
                c02 = fp.fp2_add(c02, c11)
                return [c00, c01, c02]

        if n == 4 and glen == 4:

            # This special case can be used as the general case it is expensive. Maybe with anoth bound for n should fine
            S = n
            for i in range(0, n // 2, 1):
                S = S + n - 1 - 2 * i

            c = list([[0, 0]] * S)
            nf = n // 2  # floor of n/2
            nc = -(-n // 2)  # Ceiling of n/2

            for i in range(0, n, 1):
                c[i] = fp.fp2_mul(f[i], g[i])
            k = n
            for i in range(0, nf, 1):
                for j in range(0, n - 2 * i - 1, 1):
                    c[k] = fp.fp2_mul(fp.fp2_add(f[i], f[i + j + 1]),
                                      fp.fp2_add(g[i], g[i + j + 1]))
                    c[k] = fp.fp2_sub(c[k], fp.fp2_add(c[i], c[i + j + 1]))
                    k = k + 1

            c[n - 1] = list(c[0])
            for i in range(1, nf, 1):
                for j in range(1, n - 2 * i, 1):
                    c[n + 2 * i - 1 + j] = fp.fp2_add(
                        c[n + 2 * i - 1 + j], c[(1 + i) * n - i**2 - 1 + j])

            for i in range(1, nc, 1):
                c[n + 2 * i - 1] = fp.fp2_add(c[n + 2 * i - 1], c[i])

            delta = n - 1
            return c[delta:(n + delta)]

        # The goal is to divide the multiplication using karatsuba style multiplication
        # but saving multiplications (some higher monomials can be omited)
        l1 = n // 2  # floor(n / 2)
        l0 = n - l1  # ceil( n / 2)

        # We proceed by spliting f(x) as f0(x^2) + x * f1(x^2)
        f1len = flen // 2  # floor(flen / 2)
        f0len = flen - f1len  # ceil( flen / 2)
        f0 = [list(f[2 * i + 0]) for i in range(0, f0len, 1)]
        f1 = [list(f[2 * i + 1]) for i in range(0, f1len, 1)]

        # We proceed by spliting g(x) as g0(x^2) + x * g1(x^2)
        g1len = glen // 2  # floor(glen / 2)
        g0len = glen - g1len  # ceil( geln / 2)
        g0 = [list(g[2 * i + 0]) for i in range(0, g0len, 1)]
        g1 = [list(g[2 * i + 1]) for i in range(0, g1len, 1)]

        # Middle part like karatsuba
        f01 = [fp.fp2_add(f0[i], f1[i])
               for i in range(0, f1len, 1)] + f0[f1len:]
        g01 = [fp.fp2_add(g0[i], g1[i])
               for i in range(0, g1len, 1)] + g0[g1len:]

        # product f0 * g0 must has degree at most x^l0
        n0 = f0len + g0len - 1
        if n0 > l0:
            n0 = l0
        fg_0 = poly_mul_modxn(n0, f0, f0len, g0, g0len)

        # product f01 * g01 must has degree at most x^l1
        n01 = f0len + g0len - 1
        if n01 > l1:
            n01 = l1
        fg_01 = poly_mul_modxn(n01, f01, f0len, g01, g0len)

        # product f1 * g1 must has degree at most x^l1
        n1 = f1len + g1len - 1
        if n1 > l1:
            n1 = l1
        fg_1 = poly_mul_modxn(n1, f1, f1len, g1, g1len)

        # Computing the middle part
        fg_01 = [fp.fp2_sub(fg_01[i], fg_0[i])
                 for i in range(0, n01, 1)] + fg_01[n01:]
        fg_01 = [fp.fp2_sub(fg_01[i], fg_1[i])
                 for i in range(0, n1, 1)] + fg_01[n1:]

        # Unifying the computations
        fg = [[0, 0]] * n
        for i in range(0, n0, 1):
            fg[2 * i] = list(fg_0[i])

        for i in range(0, n01, 1):
            fg[2 * i + 1] = list(fg_01[i])

        for i in range(0, n1 - 1, 1):
            fg[2 * i + 2] = fp.fp2_add(fg[2 * i + 2], fg_1[i])

        if 2 * n1 < n:
            fg[2 * n1] = fp.fp2_add(fg[2 * n1], fg_1[n1 - 1])

        return fg

    # Next funtion computes the central part polynomial of f * g mod (x^m - 1)
    # Next two functions are based on the work title "The Middle Product Algorithm, I." by G. Hanrot. M. Quercia, and P. Zimmermann
    def quasi_poly_mul_middle(g, glen, f, flen):

        assert glen == len(g)
        assert flen == len(f)

        if glen == 0:
            return []

        if glen == 1:
            return [fp.fp2_mul(g[0], f[0])]

        glen0 = glen // 2  # floor(glen / 2)
        glen1 = glen - glen0  # ceil(glen / 2)

        A = poly_mul_middle(
            g[glen0:],
            glen1,
            [
                fp.fp2_add(f[i], f[i + glen1])
                for i in range(0, 2 * glen1 - 1, 1)
            ],
            2 * glen1 - 1,
        )

        if glen % 2 == 0:
            B = poly_mul_middle(
                [fp.fp2_sub(g[glen1 + i], g[i]) for i in range(0, glen0, 1)],
                glen0,
                f[glen1:(3 * glen1 - 1)],
                2 * glen1 - 1,
            )
        else:
            B = poly_mul_middle(
                [g[glen0]] +
                [fp.fp2_sub(g[glen1 + i], g[i]) for i in range(0, glen0, 1)],
                glen1,
                f[glen1:(3 * glen1 - 1)],
                2 * glen1 - 1,
            )

        C = poly_mul_middle(
            g[:glen0],
            glen0,
            [
                fp.fp2_add(f[glen1 + i], f[2 * glen1 + i])
                for i in range(0, 2 * glen0 - 1, 1)
            ],
            2 * glen0 - 1,
        )

        assert len(A) == glen1
        assert len(B) == glen1
        assert len(C) == glen0
        return [fp.fp2_sub(A[i], B[i]) for i in range(0, glen1, 1)
                ] + [fp.fp2_add(C[i], B[i]) for i in range(0, glen0, 1)]

    # Next funtion computes the central part polynomial of f * g mod (x^m - 1)
    # This functions is an extension of quasi_poly_mul_middle()
    def poly_mul_middle(g, glen, f, flen):

        if (glen == (flen // 2)) and (flen % 2 == 0):
            # Here deg(f) = 2d - 1 and deg(g) = d - 1
            return quasi_poly_mul_middle(g, glen, f[1:] + [[0, 0]], flen)

        elif (glen == ((flen // 2) + 1)) and (flen % 2 == 0):
            # Here deg(f) = 2d - 1 and deg(g) = d
            return quasi_poly_mul_middle(g, glen, [[0, 0]] + f, flen + 1)

        elif (glen == ((flen // 2) + 1)) and (flen % 2 == 1):
            # Here deg(f) = 2d and deg(g) = d
            return quasi_poly_mul_middle(g, glen, f, flen)

        else:

            # Unbalanced case when glen < floor(flen/2) and ceil(flen/2) < glen
            # This case only happens when using scaled remainder trees
            if glen == 0:
                return []

            # low part
            F0len = flen - glen
            F0 = f[:F0len]
            F0 = F0[::-1]
            G = g[::-1]
            fg_low = poly_mul_modxn(glen - 1, F0, F0len, G, glen)
            fg_low = fg_low[::-1]

            # high part
            F1 = f[F0len:]
            fg_high = poly_mul_modxn(glen, F1, glen, g, glen)

            return [
                fp.fp2_add(fg_low[i], fg_high[i])
                for i in range(0, glen - 1, 1)
            ] + [fg_high[glen - 1]]

    # Next function computes the product of two polynomials g(x) annd f(x) such that
    # g(x) = x^dg * g(1/x) and f(x) = x^df * f(1/x) where deg g = dg, and deg f = df
    def poly_mul_selfreciprocal(g, glen, f, flen):

        if glen == 0 and flen == 0:
            return []

        if glen == 1 and flen == 1:
            return [fp.fp2_mul(g[0], f[0])]

        if glen == 2 and flen == 2:

            h = [[0, 0]] * 3
            h[0] = fp.fp2_mul(g[0], f[0])
            h[1] = fp.fp2_add(h[0], h[0])
            h[2] = list(h[0])
            return h

        if glen == 3 and flen == 3:

            h = [[0, 0]] * 5
            h[0] = fp.fp2_mul(g[0], f[0])
            h[2] = fp.fp2_mul(g[1], f[1])
            g01 = fp.fp2_add(g[0], g[1])
            f01 = fp.fp2_add(f[0], f[1])
            h[1] = fp.fp2_mul(g01, f01)
            h[2] = fp.fp2_add(h[2], h[0])
            h[1] = fp.fp2_sub(h[1], h[2])
            h[2] = fp.fp2_add(h[2], h[0])
            h[3] = list(h[1])
            h[4] = list(h[0])
            return h

        if glen == 4 and flen == 4:

            h = [[0, 0]] * 7
            h[0] = fp.fp2_mul(g[0], f[0])
            h[3] = fp.fp2_mul(g[1], f[1])
            g01 = fp.fp2_add(g[0], g[1])
            f01 = fp.fp2_add(f[0], f[1])
            h[2] = fp.fp2_mul(g01, f01)
            h[2] = fp.fp2_sub(h[2], h[0])
            h[1] = fp.fp2_sub(h[2], h[3])
            h[3] = fp.fp2_add(h[3], h[0])
            h[3] = fp.fp2_add(h[3], h[3])
            h[4] = list(h[2])
            h[5] = list(h[1])
            h[6] = list(h[0])
            return h

        if glen == 5 and flen == 5:

            h = [[0, 0]] * 9
            g10 = fp.fp2_sub(g[1], g[0])
            f01 = fp.fp2_sub(f[0], f[1])
            h[1] = fp.fp2_mul(g10, f01)
            g20 = fp.fp2_sub(g[2], g[0])
            f02 = fp.fp2_sub(f[0], f[2])
            h[2] = fp.fp2_mul(g20, f02)
            g21 = fp.fp2_sub(g[2], g[1])
            f12 = fp.fp2_sub(f[1], f[2])
            h[3] = fp.fp2_mul(g21, f12)

            h[0] = fp.fp2_mul(g[0], f[0])
            g1f1 = fp.fp2_mul(g[1], f[1])
            g2f2 = fp.fp2_mul(g[2], f[2])

            t = fp.fp2_add(g1f1, h[0])
            h[1] = fp.fp2_add(h[1], t)
            h[3] = fp.fp2_add(h[3], h[1])
            h[4] = fp.fp2_add(t, g2f2)
            h[4] = fp.fp2_add(h[4], t)
            h[2] = fp.fp2_add(h[2], t)
            h[2] = fp.fp2_add(h[2], g2f2)
            h[3] = fp.fp2_add(h[3], g1f1)
            h[3] = fp.fp2_add(h[3], g2f2)

            h[5] = list(h[3])
            h[6] = list(h[2])
            h[7] = list(h[1])
            h[8] = list(h[0])
            return h

        if glen == flen and (glen % 2) == 1:

            # Case two degree polynomials of same even degree
            len0 = (glen + 1) // 2
            len1 = glen // 2

            g0 = [[0, 0]] * len0
            f0 = [[0, 0]] * len0
            g1 = [[0, 0]] * len1
            f1 = [[0, 0]] * len1

            # We proceed by applying the same idea as in poly_mul_modxn
            # ---
            for i in range(0, len0, 1):
                g0[i] = list(g[2 * i])
                f0[i] = list(f[2 * i])

            h0 = poly_mul_selfreciprocal(g0, len0, f0, len0)

            # ---
            for i in range(0, len1, 1):
                g1[i] = list(g[2 * i + 1])
                f1[i] = list(f[2 * i + 1])

            h1 = poly_mul_selfreciprocal(g1, len1, f1, len1)

            # ---
            for i in range(0, len1, 1):
                g0[i] = fp.fp2_add(g0[i], g1[i])
                f0[i] = fp.fp2_add(f0[i], f1[i])

            for i in range(0, len1, 1):
                g0[i + 1] = fp.fp2_add(g0[i + 1], g1[i])
                f0[i + 1] = fp.fp2_add(f0[i + 1], f1[i])

            h01 = poly_mul_selfreciprocal(g0, len0, f0, len0)

            # Mixing the computations
            for i in range(0, 2 * len0 - 1, 1):
                h01[i] = fp.fp2_sub(h01[i], h0[i])

            for i in range(0, 2 * len1 - 1, 1):
                h01[i] = fp.fp2_sub(h01[i], h1[i])

            for i in range(0, 2 * len1 - 1, 1):
                h01[i + 1] = fp.fp2_sub(h01[i + 1], h1[i])

            for i in range(0, 2 * len1 - 1, 1):
                h01[i + 1] = fp.fp2_sub(h01[i + 1], h1[i])

            for i in range(0, 2 * len1 - 1, 1):
                h01[i + 2] = fp.fp2_sub(h01[i + 2], h1[i])

            for i in range(1, 2 * len0 - 1, 1):
                h01[i] = fp.fp2_sub(h01[i], h01[i - 1])

            # Unifying the computations
            hlen = 2 * glen - 1
            h = [[0, 0]] * hlen

            for i in range(0, 2 * len0 - 1, 1):
                h[2 * i] = list(h0[i])

            for i in range(0, 2 * len0 - 2, 1):
                h[2 * i + 1] = list(h01[i])

            for i in range(0, 2 * len1 - 1, 1):
                h[2 * i + 2] = fp.fp2_add(h[2 * i + 2], h1[i])

            return h

        if glen == flen and (glen % 2) == 0:

            # Case two degree polynomials of same odd degree

            half = glen // 2
            h0 = poly_mul(g[:half], half, f[:half], half)
            h1 = poly_mul(g[:half], half, f[half:], half)

            h = [[0, 0]] * (2 * glen - 1)
            for i in range(0, glen - 1, 1):
                h[i] = fp.fp2_add(h[i], h0[i])

            for i in range(0, glen - 1, 1):
                h[2 * glen - 2 - i] = fp.fp2_add(h[2 * glen - 2 - i], h0[i])

            for i in range(0, glen - 1, 1):
                h[half + i] = fp.fp2_add(h[half + i], h1[i])

            for i in range(0, glen - 1, 1):
                h[glen + half - 2 - i] = fp.fp2_add(h[glen + half - 2 - i],
                                                    h1[i])

            return h

        # General case
        hlen = glen + flen - 1
        m = (hlen + 1) // 2
        h = poly_mul_modxn(m, g, glen, f, flen)
        h = h + [[0, 0]] * (hlen - m)
        for i in range(m, hlen, 1):
            h[i] = list(h[hlen - 1 - i])

        return h

    # Product tree of given list of polynomials
    def product_tree(f, n):

        if n == 0:
            return {'left': None, 'right': None, 'poly': [[1, 0]], 'deg': 0}

        if n == 1:

            # No multiplication is required
            return {
                'left': None,
                'right': None,
                'poly': list(f[0]),
                'deg': len(f[0]) - 1,
            }

        else:

            m = n - (n // 2)
            left = product_tree(f[:m], m)
            right = product_tree(f[m:], n - m)
            return {
                'left':
                left,
                'right':
                right,
                'poly':
                poly_mul(left['poly'], left['deg'] + 1, right['poly'],
                         right['deg'] + 1),
                'deg':
                left['deg'] + right['deg'],
            }

    # Product tree of given list of self reciprocal polynomials
    def product_selfreciprocal_tree(f, n):

        if n == 0:
            return {'left': None, 'right': None, 'poly': [[1, 0]], 'deg': 0}

        if n == 1:

            # No multiplication is required
            return {
                'left': None,
                'right': None,
                'poly': list(f[0]),
                'deg': len(f[0]) - 1,
            }

        else:

            m = n - (n // 2)
            left = product_selfreciprocal_tree(f[:m], m)
            right = product_selfreciprocal_tree(f[m:], n - m)
            return {
                'left':
                left,
                'right':
                right,
                'poly':
                poly_mul_selfreciprocal(left['poly'], left['deg'] + 1,
                                        right['poly'], right['deg'] + 1),
                'deg':
                left['deg'] + right['deg'],
            }
            # Now, the resultant of two polinomials corresponds with product of
            # all g(x) mod f_i(x) when each f_i is a linear polynomial

    def product(list_g_mod_f, n):

        # When finishing tests, remove the below asserts
        if n == 0:
            return [1, 0]

        assert len(list_g_mod_f) == n

        out = list(list_g_mod_f[0][0])
        for j in range(1, n, 1):

            out = fp.fp2_mul(out, list_g_mod_f[j][0])

        return out

    return attrdict(locals())
Beispiel #10
0
def print_strategy(ctx):
    """
    draw graphs
    """
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    if setting.algorithm == 'csidh':
        n = algo.params.n
        m = algo.params.m
        L = algo.params.L
        rounds = algo.gae.rounds
        geometric_serie = algo.gae.geometric_serie

        BATCHES = lambda n, sigma: [
            len([j for j in range(i, n, sigma)]) for i in range(sigma)
        ]

        if len(set(m)) > 1:
            # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i)
            LABEL_m = 'different_bounds'
        else:
            # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i)
            LABEL_m = 'with_same_bounds'

        if setting.verbose:
            verb = '-suitable'
        else:
            verb = '-classical'

        # List of Small Odd Primes, L := [l_0, ..., l_{n-1}]
        m_prime = [geometric_serie(m[k], L[k]) for k in range(n)]
        r_out, L_out, R_out = rounds(m_prime[::-1], n)
        for j in range(0, len(r_out), 1):

            R_out[j] = list([L[::-1][k] for k in R_out[j]])
            L_out[j] = list([L[::-1][k] for k in L_out[j]])

        f = open(strategy_data + setting.algorithm + '-' + setting.prime +
                 '-' + setting.style + '-' + setting.formula + '-' + LABEL_m +
                 verb)
        print("// Strategies to be read from a file")
        S_out = []
        for i in range(0, len(r_out), 1):

            tmp = f.readline()
            tmp = [int(b) for b in tmp.split()]
            S_out.append(tmp)

        f.close()

        filename = (setting.algorithm + '-' + setting.prime + '-' +
                    setting.style + '-' + setting.formula + '-' + LABEL_m +
                    verb)

        # ----
        for idx in range(0, len(S_out), 1):
            S = S_out[idx]
            n = len(S) + 1

            # Strategy written as a graph
            vertexes, vertex_colors, edges, edge_colors = strategy_evaluation(
                S, n)

            # Simba method written as a graph
            # vertexes, vertex_colors, edges, edge_colors = simba(n, 3)

            # All the Discrete Right Triangle
            # vertexes, vertex_colors, edges, edge_colors = DRT(n)
            G = nx.Graph()

            # Adding nodes in specific positions
            G.add_nodes_from(list(range(len(vertexes))))

            nx.set_node_attributes(G, vertexes, 'pos')
            # Adding edges with specific colors
            for i in range(len(edges)):
                G.add_edge(edges[i][0], edges[i][1], color=edge_colors[i])

            # Setting variables for a pretty plot of the graph
            edges = G.edges()
            edge_colors = [G[u][v]['color'] for u, v in edges]
            weights = [6 for u, v in edges]
            vertex_sizes = [24] * len(vertexes)

            # Finally, the graph will be plotted
            plt.figure(1, figsize=(17, 17))
            nx.draw(
                G,
                vertexes,
                node_color=['black'] * len(vertexes),
                node_size=vertex_sizes,
                edge_color=edge_colors,
                width=weights,
                edge_labels=True,
            )

            # Saving the graph as a .PNG figure
            plt.savefig(filename + '-id' + str(idx) + '.png')
            print("saving graph: " + filename + '-id' + str(idx) + '.png')
            # plt.show()
            plt.close()

        print(
            "// The strategies have been plotted and stored in the current directory"
        )
    else:
        print("bsidh not yet implemented")
        click.Exit(1)

    def DRT(n):

        vertexes = dict()  # list of the position of each node
        vertex_colors = (
            []
        )  # color of each node: red for the leaves, otherwise color is set to white
        acc = 0

        # Different shape of the isogeny graph
        for i in range(n):
            for j in range(n - 1 - i):
                vertex_colors.append('black')
                vertexes[acc] = (i, -j)
                acc += 1

        return vertexes, vertex_colors, [], []

    def simba(n, sigma):

        vertexes = dict()  # list of the position of each node
        edges = []  # edges of the isogeny triangle

        edge_colors = (
            []
        )  # color of each edge: blue for scalar multiplications, and orange for isogeny evalutions
        vertex_colors = (
            []
        )  # color of each node: red for the leaves, otherwise color is set to white

        batches = BATCHES(n, sigma)
        t = -1
        acc = 0
        for i in range(sigma):

            t += 1
            vertexes[t] = (acc, 0)  # Initial vertex of the current batch
            vertex_colors.append('black')

            t += 1
            vertexes[t] = (acc, -n + batches[i])  # Root of the current batch
            vertex_colors.append('black')

            edges.append((t - 1, t))  # New edge to be added
            edge_colors.append(
                'tab:blue')  # Color of this type of edge is always blue

            vertex = vertexes[t]
            for j in range(batches[i] - 1):

                t += 1
                vertexes[t] = (
                    vertexes[t - 1][0],
                    vertexes[t - 1][1] - batches[i] + j + 1,
                )  # Next leaf
                vertex_colors.append('black')

                edges.append((t - 1, t))  # New edge to be added
                edge_colors.append(
                    'tab:blue')  # Color of this type of edge is always blue

                t += 1
                vertexes[t] = (vertexes[t - 2][0] + 1, vertexes[t - 2][1])
                vertex_colors.append('black')

                edges.append((t - 2, t))  # New edge to be added
                edge_colors.append(
                    'tab:red')  # Color of this type of edge is always blue

            acc += batches[i]

        return vertexes, vertex_colors, edges, edge_colors

    return attrdict(name='print-strategy', **locals())
Beispiel #11
0
def MontgomeryCurve(prime, style):

    if True:  # algorithm == 'csidh':
        # this is montgomery.py currently
        #A = parameters['csidh']['A'] <--- Not required
        L = parameters['csidh'][prime]['L']
        n = parameters['csidh'][prime]['n']
        exponent_of_two = parameters['csidh'][prime]['exponent_of_two']

        p = (2**(exponent_of_two)) * reduce(
            lambda x, y: (x * y), L) - 1  # p := 4 * l_0 * ... * l_n - 1
        # p_minus_one_halves = (p - 1) // 2  # (p - 1) / 2
        p_minus_one_halves = parameters['csidh'][prime]['p_minus_one_halves']
        validation_stop = sum([bitlength(l_i) for l_i in L]) / 2.0 + 2
        FiniteFieldClass = PrimeField
    else:
        assert False, "bsidh not refactored yet"
        # this is for a possible future where we have a unified montgomery.py
        # for csidh and bsidh. the code in this else block was moved from fp.py

        f = open(sop_data + prime)

        # The prime to be used
        p = f.readline()
        self.p = int(p, 16)

        # List corresponding (p + 1)
        Lp = f.readline()
        Lp = [int(lp) for lp in Lp.split()]
        # exponent_of_twop = Lp[0]
        # Lp = Lp[1:]
        Ep = f.readline()
        Ep = [int(ep) for ep in Ep.split()]
        assert len(Ep) == len(Lp)
        np = len(Lp)

        # List corresponding (p - 1)
        Lm = f.readline()
        Lm = [int(lm) for lm in Lm.split()]
        Em = f.readline()
        Em = [int(em) for em in Em.split()]
        assert len(Em) == len(Lm)
        nm = len(Lm)

        f.close()

        # pp = (2**exponent_of_twop) * reduce(lambda x,y : (x*y), [ Lp[i]**Ep[i] for i in range(0, np, 1)  ])
        pp = reduce(lambda x, y: (x * y),
                    [Lp[i]**Ep[i] for i in range(0, np, 1)])
        pm = reduce(lambda x, y: (x * y),
                    [Lm[i]**Em[i] for i in range(0, nm, 1)])
        assert (p + 1) % pp == 0
        assert (p - 1) % pm == 0

        if p % 4 == 1:
            print("// Case p = 1 mod 4 is not implemented yet!")
            exit(-1)

        p_minus_one_halves = (p - 1) // 2
        p_minus_3_quarters = (p - 3) // 4
        #FiniteFieldClass = QuadraticField

    ff = FiniteFieldClass(p)
    # print("// Shortest Differential Addition Chains (SDAC) for each l_i;")
    # List of Small odd primes, L := [l_0, ..., l_{n-1}]
    # print("// SDAC's to be read from a file")
    path = sdacs_data + prime
    SDACS = filename_to_list_of_lists_of_ints(path)
    if len(SDACS) == 0:
        print("// SDAC's to be computed")
        SDACS = generate_sdacs(L)
        print("// Storing SDAC's in a file")
        write_list_of_lists_of_ints_to_file(path, SDACS)
    SDACS_LENGTH = list(map(len, SDACS))

    cMUL = lambda l: numpy.array([
        4.0 * (SDACS_LENGTH[L.index(l)] + 2),
        2.0 * (SDACS_LENGTH[L.index(l)] + 2),
        6.0 * (SDACS_LENGTH[L.index(l)] + 2) - 2.0,
    ])
    C_xmul = list(map(cMUL, L))  # list of the costs of each [l]P

    SQR = 1.00
    ADD = 0.00

    random = SystemRandom()

    def elligator(A):

        Ap = (A[0] + A[0])
        Ap = (Ap - A[1])
        Ap = (Ap + Ap)
        Cp = A[1]

        u = ff(random.randint(2, p_minus_one_halves))
        u_squared = (u**2)

        u_squared_plus_one = (u_squared + 1)
        u_squared_minus_one = (u_squared - 1)

        C_times_u_squared_minus_one = (Cp * u_squared_minus_one)
        AC_times_u_squared_minus_one = (Ap * C_times_u_squared_minus_one)

        tmp = (Ap**2)
        tmp = (tmp * u_squared)
        aux = (C_times_u_squared_minus_one**2)
        tmp = (tmp + aux)
        tmp = (AC_times_u_squared_minus_one * tmp)

        alpha, beta = 0, u
        alpha, beta = cswap(alpha, beta, tmp == 0)
        u_squared_plus_one = (alpha * u_squared_plus_one)
        alpha = (alpha * C_times_u_squared_minus_one)

        Tp_X = (Ap + alpha)
        Tm_X = (Ap * u_squared)
        Tm_X = (Tm_X + alpha)
        Tm_X = (-Tm_X)

        tmp = (tmp + u_squared_plus_one)
        Tp_X, Tm_X = cswap(Tp_X, Tm_X, not tmp.issquare())

        return (
            [Tp_X, C_times_u_squared_minus_one],
            [Tm_X, C_times_u_squared_minus_one],
        )

    def generate_sdacs(L):
        return list(
            map(sdac, L)
        )  # Shortest Differential Addition Chains for each small odd prime l in L

    def measure(x):
        """
        SQR = 1.00
        # In F_p, we have SQR_{F_p} = SQR x MUL_{F_p}
        ADD = 0.00
        # In F_p, we have ADD_{F_p} = ADD x MUL_{F_p}
        """
        return x[0] + SQR * x[1] + ADD * x[2]

    def dacs(l, r0, r1, r2, chain):
        '''
        dacs()
        inputs: a small odd prime number l, three integer numbers, and a list
        output: all the differential additions chains corresponding with the input l

        NOTE: this is a recursive approach
        '''
        if r2 == l:

            return [(chain, r2)]
        elif r2 < l and len(chain) <= 1.5 * math.log(l, 2):

            return dacs(l, r0, r2, r2 + r0, chain + [1]) + dacs(
                l, r1, r2, r2 + r1, chain + [0])
        else:
            return []

    def sdac(l):
        '''
        sdac()
        input: a small odd prime number l
        output: the shortest differential additions chains corresponding with the input l

        NOTE: this function uses a recursive function
        '''
        all_dacs = dacs(l, 1, 2, 3, [])
        return min(all_dacs, key=lambda t: len(t[0]))[0]

    def affine_to_projective(affine):
        """
        input : the affine Montgomery coefficient A=A'/C with C=1
        output: projective Montgomery constants A24 := A' + 2C and C24 := 4C
                where E : y^2 = x^3 + (A'/C)*x^2 + x
        """
        return [affine + ff(2), ff(4)]

    def coeff(A):
        '''
        ----------------------------------------------------------------------
        coeff()
        input : projective Montgomery constants A24 := A + 2C and C24 := 4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the affine Montgomery coefficient A/C
        ----------------------------------------------------------------------
        '''
        output = (A[0] + A[0])  # (2 * A24)
        output = (output - A[1])  # (2 * A24) - C24
        C24_inv = (A[1]**-1)  # 1 / (C24)
        output = (output + output)  # 4*A = 2[(2 * A24) - C24]
        output = (output * C24_inv)  # A/C = 2[(2 * A24) - C24] / C24

        return output

    def isinfinity(P):
        """
        isinfinity(P) determines if x(P) := (XP : ZP) = (1 : 0)
        """
        return P[1] == 0

    def isequal(P, Q):
        """ isequal(P, Q) determines if x(P) = x(Q) """
        return (P[0] * Q[1]) == (P[1] * Q[0])

    def xdbl(P, A):
        '''
        ----------------------------------------------------------------------
        xdbl()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, and
                the  projective Montgomery constants A24:= A + 2C and C24:=4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the projective Montgomery x-coordinate point x([2]P)
        ----------------------------------------------------------------------
        '''
        t_0 = (P[0] - P[1])
        t_1 = (P[0] + P[1])
        t_0 = (t_0**2)
        t_1 = (t_1**2)
        Z = (A[1] * t_0)
        X = (Z * t_1)
        t_1 = (t_1 - t_0)
        t_0 = (A[0] * t_1)
        Z = (Z + t_0)
        Z = (Z * t_1)

        return [X, Z]

    def xadd(P, Q, PQ):
        '''
        ----------------------------------------------------------------------
        xadd()
        input : the projective Montgomery x-coordinate points x(P) := XP/ZP,
                x(Q) := XQ/ZQ, and x(P-Q) := XPQ/ZPQ
        output: the projective Montgomery x-coordinate point x(P+Q)
        ----------------------------------------------------------------------
        '''
        a = (P[0] + P[1])
        b = (P[0] - P[1])
        c = (Q[0] + Q[1])
        d = (Q[0] - Q[1])
        a = (a * d)
        b = (b * c)
        c = (a + b)
        d = (a - b)
        c = (c**2)
        d = (d**2)
        X = (PQ[1] * c)
        Z = (PQ[0] * d)
        return [X, Z]

    # Modificar esta parte para usar cadenas de addicion
    def xmul(P, A, j):
        '''
        ----------------------------------------------------------------------
        xmul()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the
                projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, and an positive integer j
        output: the projective Montgomery x-coordinate point x([L[j]]P)
        ----------------------------------------------------------------------
        '''
        P2 = xdbl(P, A)
        R = [P, P2, xadd(P2, P, P)]

        for i in range(SDACS_LENGTH[j] - 1, -1, -1):

            if isinfinity(R[SDACS[j][i]]):
                T = xdbl(R[2], A)
            else:
                T = xadd(R[2], R[SDACS[j][i] ^ 1], R[SDACS[j][i]])

            R[0] = list(R[SDACS[j][i] ^ 1])
            R[1] = list(R[2])
            R[2] = list(T)

        return R[2]

    def cofactor_multiples(P, A, points):
        '''
        ----------------------------------------------------------------------
        cofactor_multiples()
        input : a projective Montgomery x-coordinate point x(P) := XP/ZP, the
                projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, and subset of |[0, n]|
        output: the projective Montgomery x-coordinate points x([(p+1) / l_0]P),
                x([(p+1) / l_1]P), ..., x([(p+1) / l_{n-1}]P).
        ----------------------------------------------------------------------
        '''
        n = len(points)
        if n == 1:
            # In this recursion level we have an order-l point
            return [P]
        elif n > 0:
            # We proceed by applying a divide-and-conquer procedure
            h = n // 2
            if h > 0:

                # 1st half
                first_half = []
                second_P = P
                for j in range(h):
                    second_P = xmul(second_P, A, points[j])
                    first_half.append(points[j])

                # 2nd half
                second_half = []
                first_P = P
                for j in range(h, n):
                    first_P = xmul(first_P, A, points[j])
                    second_half.append(points[j])

                return cofactor_multiples(first_P, A,
                                          first_half) + cofactor_multiples(
                                              second_P, A, second_half)

            return []

    def isfullorder(seq):
        tmp = [not isinfinity(seq_i) for seq_i in seq]
        return reduce(lambda x, y: (x and y), tmp)

    def generators(A):

        output = [[0, 0], [0, 0]] if style != 'wd1' else [[0, 0], [1, 1]]
        while [0, 0] in output:

            T_p, T_m = elligator(A)
            for i in range(0, exponent_of_two, 1):
                T_p = xdbl(T_p, A)

            if isfull_order(cofactor_multiples(T_p, A, range(
                    0, n, 1))) and output[0] == [0, 0]:
                output[0] = list(T_p)

            if style != 'wd1':
                for i in range(0, exponent_of_two, 1):
                    T_m = xdbl(T_m, A)
                if isfull_order(cofactor_multiples(T_m, A, range(
                        0, n, 1))) and output[1] == [0, 0]:
                    output[1] = list(T_m)

        return output[0], output[1]

    def crisscross(alpha, beta, gamma, delta):

        t_1 = (alpha * delta)
        t_2 = (beta * gamma)
        return (t_1 + t_2), (t_1 - t_2)

    def issupersingular(A):

        while True:

            T_p, _ = elligator(A)
            for i in range(0, exponent_of_two, 1):
                T_p = xdbl(T_p, A)

            P = cofactor_multiples(T_p, A, range(0, n, 1))

            bits_of_the_order = 0
            for i in range(0, n, 1):

                if isinfinity(P[i]) == False:

                    Q = xmul(P[i], A, i)

                    if isinfinity(Q) == False:
                        return False

                    bits_of_the_order += bitlength(L[i])
                    if bits_of_the_order > validation_stop:
                        return True

    return attrdict(**locals())
Beispiel #12
0
def Tvelu(curve):

    fp = curve.fp
    global_L = curve.L

    cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)])
    cISOG = lambda l: numpy.array([
        (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0),
        (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0),
        (3.0 * l - 7.0 + isequal[l == 3] * 6.0),
    ])

    C_xEVAL = list(
        map(cEVAL,
            global_L))  # list of the costs of each degree-l isogeny evaluation
    C_xISOG = list(map(
        cISOG,
        global_L))  # list of the costs of each degree-l isogeny construction

    K = None
    ''' -------------------------------------------------------------------------
        yDBL()
        input : a projective Twisted Edwards y-coordinate point y(P) := YP/WP,
                and the projective Montgomery constants A24:= A + 2C and C24:=4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the projective Twisted Edwards y-coordinate point y([2]P)
        ------------------------------------------------------------------------- '''
    def yDBL(P, A):

        t_0 = fp.fp_sqr(P[0])
        t_1 = fp.fp_sqr(P[1])
        Z = fp.fp_mul(A[1], t_0)
        X = fp.fp_mul(Z, t_1)
        t_1 = fp.fp_sub(t_1, t_0)
        t_0 = fp.fp_mul(A[0], t_1)
        Z = fp.fp_add(Z, t_0)
        Z = fp.fp_mul(Z, t_1)
        return [fp.fp_sub(X, Z), fp.fp_add(X, Z)]

    ''' -------------------------------------------------------------------------
        yADD()
        input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP,
                y(Q) := YQ/WQ, and y(P-Q) := YPQ/QPQ
        output: the projective Twisted Edwards y-coordinate point y(P+Q)
        ------------------------------------------------------------------------- '''

    def yADD(P, Q, PQ):

        a = fp.fp_mul(P[1], Q[0])
        b = fp.fp_mul(P[0], Q[1])
        c = fp.fp_add(a, b)
        d = fp.fp_sub(a, b)
        c = fp.fp_sqr(c)
        d = fp.fp_sqr(d)

        xD = fp.fp_add(PQ[1], PQ[0])
        zD = fp.fp_sub(PQ[1], PQ[0])
        X = fp.fp_mul(zD, c)
        Z = fp.fp_mul(xD, d)
        return [fp.fp_sub(X, Z), fp.fp_add(X, Z)]

    ''' -------------------------------------------------------------------------
        KPs()
        input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP,
                the projective Montgomery constants A24:= A + 2C and C24:=4C where 
                E : y^2 = x^3 + (A/C)*x^2 + x, and a positive integer 0 <= i < n
        output: the list of projective Twisted Edwards y-coordinate points y(P),
                y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1
        ------------------------------------------------------------------------- '''

    def KPs(P, A, i):

        nonlocal K
        d = (global_L[i] - 1) // 2

        K = [[0, 0] for j in range(d + 1)]
        K[0] = list([fp.fp_sub(P[0], P[1]), fp.fp_add(P[0], P[1])])  # 2a
        K[1] = yDBL(K[0], A)  # 4M + 2S + 4a

        for j in range(2, d, 1):
            K[j] = yADD(K[j - 1], K[0], K[j - 2])  # 4M + 2S + 6a

        return K  # 2(l - 3)M + (l - 3)S + 3(l - 3)a

    ''' ------------------------------------------------------------------------------
        xISOG()
        input : the projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted 
                Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P)
                where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n
        output: the projective Montgomery constants a24:= a + 2c and c24:=4c where
                E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E
        ------------------------------------------------------------------------------ '''

    def xISOG(A, i):

        nonlocal K
        l = global_L[i]  # l
        bits_of_l = bitlength(l)  # Number of bits of L[i]
        d = (l - 1) // 2  # Here, l = 2d + 1

        By = K[0][0]
        Bz = K[0][1]
        for j in range(1, d, 1):

            By = fp.fp_mul(By, K[j][0])
            Bz = fp.fp_mul(Bz, K[j][1])

        bits_of_l -= 1
        constant_d_edwards = fp.fp_sub(A[0], A[1])  # 1a

        tmp_a = A[0]
        tmp_d = constant_d_edwards
        # left-to-right method for computing a^l and d^l
        for j in range(1, bits_of_l + 1):

            tmp_a = fp.fp_sqr(tmp_a)
            tmp_d = fp.fp_sqr(tmp_d)
            if ((l >> (bits_of_l - j)) & 1) != 0:

                tmp_a = fp.fp_mul(tmp_a, A[0])
                tmp_d = fp.fp_mul(tmp_d, constant_d_edwards)

        for j in range(3):

            By = fp.fp_sqr(By)
            Bz = fp.fp_sqr(Bz)

        C0 = fp.fp_mul(tmp_a, Bz)
        C1 = fp.fp_mul(tmp_d, By)
        C1 = fp.fp_sub(C0, C1)

        return [C0, C1]  # (l - 1 + 2*HW(l) - 2)M + 2(|l|_2 + 1)S + 2a

    ''' ------------------------------------------------------------------------------
        xEVAL()
        input : the projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted 
                Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P)
                where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n
        output: the projective Montgomery constants a24:= a + 2c and c24:=4c where
                E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E
        ------------------------------------------------------------------------------ '''

    def xEVAL(P, i):

        nonlocal K
        d = (global_L[i] - 1) // 2  # Here, l = 2d + 1

        Q0 = fp.fp_add(P[0], P[1])
        Q1 = fp.fp_sub(P[0], P[1])
        R0, R1 = curve.CrissCross(K[0][1], K[0][0], Q0, Q1)
        for j in range(1, d, 1):

            T0, T1 = curve.CrissCross(K[j][1], K[j][0], Q0, Q1)
            R0 = fp.fp_mul(T0, R0)
            R1 = fp.fp_mul(T1, R1)

        R0 = fp.fp_sqr(R0)
        R1 = fp.fp_sqr(R1)
        X = fp.fp_mul(P[0], R0)
        Z = fp.fp_mul(P[1], R1)

        return [X, Z]  # 2(l - 1)M + 2S + (l + 1)a

    return attrdict(name='tvelu', **locals())
Beispiel #13
0
def bsidh_main(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    curve = algo.curve
    tuned_name = ('-classical', '-suitable')[setting.tuned]
    SIDp = algo.curve.SIDp
    SIDm = algo.curve.SIDm
    SQR, ADD = algo.curve.SQR, algo.curve.ADD
    p = algo.params.p
    global_L = algo.curve.L
    coeff = curve.coeff
    random = SystemRandom()
    fp = curve.fp

    f_name = 'data/strategies/' + setting.algorithm + '-' + setting.prime + '-' + algo.formula.name + tuned_name
    try:
        f = open(resource_filename('sidh', f_name))
        print("// Strategies to be read from a file")
        # Corresponding to the list of Small Isogeny Degree, Lp := [l_0, ...,
        # l_{n-1}] [We need to include case l=2 and l=4]
        tmp = f.readline()
        tmp = [int(b) for b in tmp.split()]
        Sp = list(tmp)
        # Corresponding to the list of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}]
        tmp = f.readline()
        tmp = [int(b) for b in tmp.split()]
        Sm = list(tmp)
        f.close()
    except IOError:
        print("// Strategies to be computed")
        # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to
        # include case l=2 and l=4]
        Sp, Cp = dynamic_programming_algorithm(SIDp[::-1], len(SIDp))
        # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}]
        Sm, Cm = dynamic_programming_algorithm(SIDm[::-1], len(SIDm))
        f = open(f_name, 'w')
        f.writelines(' '.join([str(tmp) for tmp in Sp]) + '\n')
        f.writelines(' '.join([str(tmp) for tmp in Sm]) + '\n')
        f.close()
    print(
        "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The curve.measures are given in millions of field operations.\n"
        % (SQR, ADD))

    print("p := 0x%X;" % p)
    print("fp := GF(p);")
    print("_<x> := PolynomialRing(fp);")
    print("fp2<i> := ext<fp | x^2 + 1>;")
    print("Pr<x> := PolynomialRing(fp2);")

    # Reading public generators points
    f = open(resource_filename('sidh', 'data/gen/' + setting.prime))

    # x(PA), x(QA) and x(PA - QA)
    PQA = f.readline()
    PQA = [int(x, 16) for x in PQA.split()]
    PA = [list(PQA[0:2]), [0x1, 0x0]]
    QA = [list(PQA[2:4]), [0x1, 0x0]]
    PQA = [list(PQA[4:6]), [0x1, 0x0]]

    # x(PB), x(QB) and x(PB - QB)
    PQB = f.readline()
    PQB = [int(x, 16) for x in PQB.split()]
    PB = [list(PQB[0:2]), [0x1, 0x0]]
    QB = [list(PQB[2:4]), [0x1, 0x0]]
    PQB = [list(PQB[4:6]), [0x1, 0x0]]

    f.close()

    A = [[0x8, 0x0], [0x4, 0x0]]
    a = coeff(A)

    print("public_coeff := 0x%X + i * 0x%X;\n" % (a[0], a[1]))

    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    print("// Public Key Generation")
    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
    )

    # Alice's side
    print("// Private key corresponding to Alice")
    a_private = algo.gae.random_key(p + 1)
    fp.fp.set_zero_ops()
    Ra = curve.Ladder3pt(a_private, PA, QA, PQA, A)
    print("// sk_a := 0x%X;" % a_private)
    RUNNING_TIME = fp.fp.get_ops()
    print(
        "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;\n"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            curve.measure(RUNNING_TIME) / (10.0**6),
        ))
    print("// Public key corresponding to Alice")
    fp.fp.set_zero_ops()
    a_public, PB_a, QB_a, PQB_a = algo.gae.evaluate_strategy(
        True, PB, QB, PQB, A, Ra, SIDp[::-1], Sp, len(SIDp))
    RUNNING_TIME = fp.fp.get_ops()
    print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % (
        RUNNING_TIME[0] / (10.0**6),
        RUNNING_TIME[1] / (10.0**6),
        RUNNING_TIME[2] / (10.0**6),
        curve.measure(RUNNING_TIME) / (10.0**6),
    ))

    a_curve = coeff(a_public)
    print("pk_a := 0x%X + i * 0x%X;\n" % (a_curve[0], a_curve[1]))
    # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (a_curve[0], a_curve[1]) )
    # print("assert(Random(B) * (p + 1) eq B!0);")

    print("// Private key corresponding to Bob")
    b_private = algo.gae.random_key(p - 1)
    print("// sk_b := 0x%X;" % b_private)
    fp.fp.set_zero_ops()
    Rb = curve.Ladder3pt(b_private, PB, QB, PQB, A)
    RUNNING_TIME = fp.fp.get_ops()
    print(
        "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;\n"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            curve.measure(RUNNING_TIME) / (10.0**6),
        ))
    print("// Public key corresponding to Bob")
    fp.fp.set_zero_ops()
    b_public, PA_b, QA_b, PQA_b = algo.gae.evaluate_strategy(
        True, PA, QA, PQA, A, Rb, SIDm[::-1], Sm, len(SIDm))
    RUNNING_TIME = fp.fp.get_ops()
    print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % (
        RUNNING_TIME[0] / (10.0**6),
        RUNNING_TIME[1] / (10.0**6),
        RUNNING_TIME[2] / (10.0**6),
        curve.measure(RUNNING_TIME) / (10.0**6),
    ))

    b_curve = coeff(b_public)
    print("pk_b := 0x%X + i * 0x%X;\n" % (b_curve[0], b_curve[1]))
    # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (b_curve[0], b_curve[1]) )
    # print("assert(Random(B) * (p + 1) eq B!0);")

    # ======================================================
    print(
        "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    print("// Secret Sharing Computation")
    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
    )

    print("// Secret sharing corresponding to Alice")
    fp.fp.set_zero_ops()
    RB_a = curve.Ladder3pt(a_private, PA_b, QA_b, PQA_b, b_public)
    RUNNING_TIME = fp.fp.get_ops()
    print(
        "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" %
        (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            curve.measure(RUNNING_TIME) / (10.0**6),
        ))
    fp.fp.set_zero_ops()
    ss_a, _, _, _ = algo.gae.evaluate_strategy(False, PB, QB, PQB, b_public,
                                               RB_a, SIDp[::-1], Sp, len(SIDp))
    RUNNING_TIME = fp.fp.get_ops()
    print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % (
        RUNNING_TIME[0] / (10.0**6),
        RUNNING_TIME[1] / (10.0**6),
        RUNNING_TIME[2] / (10.0**6),
        curve.measure(RUNNING_TIME) / (10.0**6),
    ))

    ss_a_curve = coeff(ss_a)
    print("ss_a := 0x%X + i * 0x%X;\n" % (ss_a_curve[0], ss_a_curve[1]))
    # ss_ja = jinvariant(ss_a)
    # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (ss_a_curve[0], ss_a_curve[1]) )
    # print("jB := 0x%X + i * 0x%X;" % (ss_ja[0], ss_ja[1]) )
    # print("assert(Random(B) * (p + 1) eq B!0);")
    # print("assert(jInvariant(B) eq jB);")

    print("// Secret sharing corresponding to Bob")
    fp.fp.set_zero_ops()
    RA_b = curve.Ladder3pt(b_private, PB_a, QB_a, PQB_a, a_public)
    RUNNING_TIME = fp.fp.get_ops()
    print(
        "// kernel point generator cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" %
        (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            curve.measure(RUNNING_TIME) / (10.0**6),
        ))
    fp.fp.set_zero_ops()
    ss_b, _, _, _ = algo.gae.evaluate_strategy(False, PA, QA, PQA, a_public,
                                               RA_b, SIDm[::-1], Sm, len(SIDm))
    RUNNING_TIME = fp.fp.get_ops()
    print("// isogeny evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;" % (
        RUNNING_TIME[0] / (10.0**6),
        RUNNING_TIME[1] / (10.0**6),
        RUNNING_TIME[2] / (10.0**6),
        curve.measure(RUNNING_TIME) / (10.0**6),
    ))

    ss_b_curve = coeff(ss_b)
    print("ss_b := 0x%X + i * 0x%X;\n" % (ss_b_curve[0], ss_b_curve[1]))
    # ss_jb = jinvariant(ss_b)
    # print("B := EllipticCurve(x^3 + (0x%X + i * 0x%X )* x^2 + x);" % (ss_b_curve[0], ss_b_curve[1]) )
    # print("jB := 0x%X + i * 0x%X;" % (ss_jb[0], ss_jb[1]) )
    # print("assert(Random(B) * (p + 1) eq B!0);")
    # print("assert(jInvariant(B) eq jB);")

    print(
        "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    if ss_a_curve == ss_b_curve:
        print('\x1b[0;30;43m' + '\"Successfully passed!\";' + '\x1b[0m')
    else:
        print('\x1b[0;30;41m' +
              '\"Great Scott!... The sky is falling. NOT PASSED!!!\"' +
              '\x1b[0m')
    return attrdict(name='bsidh-main', **locals())
Beispiel #14
0
def F_p2(p):
    fp = F_p(p)

    # Addition in the quadratic extension field of fp
    def fp2_add(a, b):
        return [fp.fp_add(a[0], b[0]), fp.fp_add(a[1], b[1])]

    # Substraction in the quadratic extension field of fp
    def fp2_sub(a, b):
        return [fp.fp_sub(a[0], b[0]), fp.fp_sub(a[1], b[1])]

    # Product in the quadratic extension field of fp
    def fp2_mul(a, b):

        z0 = fp.fp_add(a[0], a[1])
        z1 = fp.fp_add(b[0], b[1])

        t = fp.fp_mul(z0, z1)
        z2 = fp.fp_mul(a[0], b[0])
        z3 = fp.fp_mul(a[1], b[1])

        c = [0, 0]
        c[0] = fp.fp_sub(z2, z3)
        c[1] = fp.fp_sub(t, z2)
        c[1] = fp.fp_sub(c[1], z3)
        return c

    # Squaring in the quadratic extension field of fp
    def fp2_sqr(a):

        z0 = fp.fp_add(a[0], a[0])
        z1 = fp.fp_add(a[0], a[1])
        z2 = fp.fp_sub(a[0], a[1])

        b = [0, 0]
        b[0] = fp.fp_mul(z1, z2)
        b[1] = fp.fp_mul(z0, a[1])
        return b

    # Inverse in the quadratic extension field of fp
    def fp2_inv(a):

        N0 = fp.fp_sqr(a[0])
        N1 = fp.fp_sqr(a[1])

        S1 = fp.fp_add(N0, N1)
        S1 = fp.fp_inv(S1)

        b = [0, 0]
        S2 = fp.fp_sub(0, a[1])
        b[0] = fp.fp_mul(S1, a[0])
        b[1] = fp.fp_mul(S1, S2)
        return b

    # Exponentiation in the quadratic extension field of fp
    def fp2_exp(a, e):

        bits_of_e = bitlength(e)
        bits_of_e -= 1
        tmp_a = list(a)
        # left-to-right method for computing a^e
        for j in range(1, bits_of_e + 1):

            tmp_a = fp2_sqr(tmp_a)
            if ((e >> (bits_of_e - j)) & 1) != 0:
                tmp_a = fp2_mul(tmp_a, a)

        return tmp_a

    # Sqrt (if exists) in the quadratic extension field of fp
    def fp2_issquare(a):

        a1 = fp2_exp(a, p_minus_3_quarters)
        alpha = fp2_sqr(a1)
        alpha = fp2_mul(alpha, a)

        alpha_conjugated = list(alpha)
        alpha_conjugated[1] = fp.fp_sub(0, alpha_conjugated[1])

        a0 = fp2_mul(alpha, alpha_conjugated)
        if a0[1] == 0 and a0[0] == (p - 1):
            # a doesn't have sqrt in fp2
            return False, None

        x0 = fp2_mul(a1, a)
        if alpha[1] == 0 and alpha[0] == (p - 1):
            return True, [fp.fp_sub(0, x0[1]), x0[0]]

        else:

            alpha[0] = fp.fp_add(alpha[0], 1)
            b = fp2_exp(alpha, p_minus_one_halves)
            b = fp2_mul(b, x0)
            return True, b

    def fp2_cswap(x, y, b):
        z0, w0 = fp.fp_cswap(x[0], y[0], b)
        z1, w1 = fp.fp_cswap(x[1], y[1], b)
        return [z0, z1], [w0, w1]

    """
    # Tests
    print("p := 0x%X;" % p)
    print("fp := GF(p);")
    print("P<x> := PolynomialRing(fp);");
    print("fp2<i> := ext<fp | x^2 + 1>;")
    print("P<x> := PolynomialRing(fp2);");

    a = [random.randint(0, p - 1), random.randint(0, p - 1)]
    b = [random.randint(0, p - 1), random.randint(0, p - 1)]
    print("a := 0x%X + 0x%X * i;" % (a[0], a[1]))
    print("b := 0x%X + 0x%X * i;" % (b[0], b[1]))

    e = random.randint(0, p - 1)
    print("e := 0x%X;" % e)

    c = fp2_add(a, b)
    print("(0x%X + 0x%X * i) eq (a+b);" % (c[0], c[1]))
    c = fp2_sub(a, b)
    print("(0x%X + 0x%X * i) eq (a-b);" % (c[0], c[1]))
    c = fp2_mul(a, b)
    print("(0x%X + 0x%X * i) eq (a*b);" % (c[0], c[1]))
    c = fp2_sqr(a)
    print("(0x%X + 0x%X * i) eq (a^2);" % (c[0], c[1]))
    c = fp2_inv(a)
    print("(0x%X + 0x%X * i) eq (1/a);" % (c[0], c[1]))
    c = fp2_exp(a, e)
    print("(0x%X + 0x%X * i) eq (a^e);" % (c[0], c[1]))
    t, c = fp2_issquare(a)
    if t:
        print("(0x%X + 0x%X * i)^2 eq a;" % (c[0], c[1]))
    """
    return attrdict(**locals())
Beispiel #15
0
def csidh_main(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    full_torsion_points = algo.curve.full_torsion_points
    coeff = algo.curve.coeff
    p = algo.params.p
    global_L = L = algo.params.L
    m = algo.params.m
    n = algo.params.n
    geometric_serie = algo.gae.geometric_serie
    rounds = algo.gae.rounds
    SQR, ADD = algo.curve.SQR, algo.curve.ADD
    set_zero_ops = algo.fp.set_zero_ops
    get_ops = algo.fp.get_ops
    validate = algo.curve.validate
    measure = algo.curve.measure
    GAE = algo.gae.GAE
    strategy_block_cost = algo.gae.strategy_block_cost
    random_key = algo.gae.random_key

    if algo.formula.name != 'tvelu':
        set_parameters_velu = algo.formula.set_parameters_velu

    temporal_m = list(set(m))
    if len(temporal_m) > 1:
        # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i)
        LABEL_m = 'different_bounds'
    else:
        # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i)
        LABEL_m = 'with_same_bounds'

    if setting.tuned:
        verb = '-suitable'
    else:
        verb = '-classical'

    # List of Small Odd Primes, L := [l_0, ..., l_{n-1}]
    m_prime = [geometric_serie(m[k], L[k]) for k in range(n)]
    r_out, L_out, R_out = rounds(m_prime[::-1], n)
    for j in range(0, len(r_out), 1):

        R_out[j] = list([L[::-1][k] for k in R_out[j]])
        L_out[j] = list([L[::-1][k] for k in L_out[j]])

    file_path = ("data/strategies/" + setting.algorithm + '-' + setting.prime +
                 '-' + setting.style + '-' + setting.formula + '-' + LABEL_m +
                 verb)
    file_path = resource_filename('sidh', file_path)
    try:
        f = open(file_path)
        print("// Strategies to be read from a file")
        S_out = []
        for i in range(0, len(r_out), 1):

            tmp = f.readline()
            tmp = [int(b) for b in tmp.split()]
            S_out.append(tmp)

        f.close()

    except IOError:

        print("// Strategies to be computed")
        C_out, L_out, R_out, S_out, r_out = strategy_block_cost(
            L[::-1], m[::-1])
        f = open(file_path, 'w')
        for i in range(0, len(r_out)):

            f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n')

        f.close()

    print(
        "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The measures are given in millions of field operations.\n"
        % (SQR, ADD))
    ''' -------------------------------------------------------------------------------------
        Framework
        ------------------------------------------------------------------------------------- '''

    print("p := 0x%X;" % p)
    print("fp := GF(p);")
    print("P<x> := PolynomialRing(fp);")
    print("fp2<i> := ext<fp | x^2 + 1>;")
    print("P<x> := PolynomialRing(fp2);")

    A = [2, 4]
    print("public_coeff := 0x%X;\n" % coeff(A))
    ''' -------------------------------------------------------------------------------------
        Main
        ------------------------------------------------------------------------------------- '''

    print("// Maximum number of degree-(\\ell_i) isogeny constructions: m_i")
    print("/*")
    printl("m", m, n // 3)
    print("*/")
    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    print("// Public Key Generation")
    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
    )

    # ------------------------------------------------------------------------- Alice
    set_zero_ops()
    public_validation = validate(A)
    assert public_validation

    a_private = random_key(m)

    print("// Private key corresponding to Alice")
    print("/*")
    printl("sk_a", a_private, n // 3)
    print("*/")

    RUNNING_TIME = get_ops()
    print("// Public key corresponding to Alice")
    print(
        "// public key validation cost  :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM,"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))

    set_zero_ops()
    if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                  (0 in temporal_m)):
        a_public = GAE(
            A,
            a_private,
            [L_out[0]],
            [R_out[0]],
            [S_out[0]],
            [temporal_m[-1]],
            m,
        )
    else:
        a_public = GAE(A, a_private, L_out, R_out, S_out, r_out, m)

    RUNNING_TIME = get_ops()
    print(
        "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))
    print("pk_a := %r;\n" % list(map(hex, a_public)))
    print("pk_a := 0x%X;\n" % coeff(a_public))

    # ------------------------------------------------------------------------- Bob
    set_zero_ops()

    b_private = random_key(m)

    print("// Private key corresponding to Bob")
    print("/*")
    printl("sk_b", b_private, n // 3)
    print("*/")

    set_zero_ops()
    public_validation = validate(A)
    assert public_validation
    RUNNING_TIME = get_ops()
    print("// Public key corresponding to Bob")
    print(
        "// public key validation cost  :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM,"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))

    set_zero_ops()
    if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                  (0 in temporal_m)):
        b_public = GAE(
            A,
            b_private,
            [L_out[0]],
            [R_out[0]],
            [S_out[0]],
            [temporal_m[-1]],
            m,
        )
    else:
        b_public = GAE(A, b_private, L_out, R_out, S_out, r_out, m)

    RUNNING_TIME = get_ops()
    print(
        "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))
    print("pk_b := %r;\n" % list(map(hex, b_public)))
    print("pk_b := 0x%X;" % coeff(b_public))

    print(
        "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    print("// Secret Sharing Computation")
    print(
        "// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
    )

    # ------------------------------------------------------------------------- Alice
    set_zero_ops()
    public_validation = validate(b_public)
    assert public_validation
    RUNNING_TIME = get_ops()
    print("// Secret sharing corresponding to Alice")
    print(
        "// public key validation cost  :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM,"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))

    set_zero_ops()
    if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                  (0 in temporal_m)):
        ss_a = GAE(
            b_public,
            a_private,
            [L_out[0]],
            [R_out[0]],
            [S_out[0]],
            [temporal_m[-1]],
            m,
        )
    else:
        ss_a = GAE(b_public, a_private, L_out, R_out, S_out, r_out, m)

    RUNNING_TIME = get_ops()

    print(
        "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))
    print("ss_a := 0x%X;\n" % coeff(ss_a))
    print(
        "expected: 0x1ADB783878BA330BB2A842E7F8B3392329A2CD3B407900E4CF6A8F13B744BFFEFF617BDE2CEBBB9CE97D32BC6FC1BCE2D88381B03B3E13CFF0651EEA82D02937"
    )

    # ------------------------------------------------------------------------- Bob
    set_zero_ops()
    public_validation = validate(a_public)
    assert public_validation
    RUNNING_TIME = get_ops()
    print("// Secret sharing corresponding to Bob")
    print(
        "// public key validation cost  :\t%2.3fM + %2.3fS + %2.3fa = %2.3fM,"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))

    set_zero_ops()
    if (len(temporal_m) == 1) or ((len(temporal_m) == 2) and
                                  (0 in temporal_m)):
        ss_b = GAE(
            a_public,
            b_private,
            [L_out[0]],
            [R_out[0]],
            [S_out[0]],
            [temporal_m[-1]],
            m,
        )
    else:
        ss_b = GAE(a_public, b_private, L_out, R_out, S_out, r_out, m)

    RUNNING_TIME = get_ops()
    print(
        "// group action evaluation cost:\t%2.3fM + %2.3fS + %2.3fa = %2.3fM;"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))
    print("ss_b := 0x%X;\n" % coeff(ss_b))

    print(
        "\n// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
    )
    if coeff(ss_a) == coeff(ss_b):
        print('\x1b[0;30;43m' + '\"Successfully passed!\";' + '\x1b[0m')
    else:
        print('\x1b[0;30;41m' +
              '\"Great Scott!... The sky is falling. NOT PASSED!!!\"' +
              '\x1b[0m')
    return attrdict(name='csidh-main', **locals())
Beispiel #16
0
def csidh_strategy(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    L = algo.params.L
    n = algo.params.n
    m = algo.params.m
    delta = algo.params.delta
    geometric_serie = algo.gae.geometric_serie
    strategy_block_cost = algo.gae.strategy_block_cost
    rounds = algo.gae.rounds
    sigma, kappa = algo.params.sigma, algo.params.kappa

    # ==========================================================================

    if len(set(m)) > 1:
        # Maximum number of degree-(l_i) isogeny constructions is m_i (different for each l_i)
        LABEL_m = 'different_bounds'
    else:
        # Maximum number of degree-(l_i) isogeny constructions is m (the same for each l_i)
        LABEL_m = 'with_same_bounds'

    if setting.tuned:
        verb = '-suitable'
    else:
        verb = '-classical'

    try:

        # List of Small Odd Primes, L := [l_0, ..., l_{n-1}]
        m_prime = [geometric_serie(m[k], L[k]) for k in range(n)]
        r_out, L_out, R_out = rounds(m_prime[::-1], n)
        for j in range(0, len(r_out), 1):

            R_out[j] = list([L[::-1][k] for k in R_out[j]])
            L_out[j] = list([L[::-1][k] for k in L_out[j]])

        f = open(strategy_data + setting.algorithm + '-' + setting.prime +
                 '-' + setting.style + '-' + setting.formula + '-' + LABEL_m +
                 verb)
        print("// Strategies to be read from a file")
        S_out = []
        for i in range(0, len(r_out), 1):

            tmp = f.readline()
            tmp = [int(b) for b in tmp.split()]
            S_out.append(tmp)

        f.close()

    except IOError:

        print("// Strategies to be computed")
        C_out, L_out, R_out, S_out, r_out = strategy_block_cost(
            L[::-1], m[::-1])
        f = open(
            strategy_data + setting.algorithm + '-' + setting.prime + '-' +
            setting.style + '-' + setting.formula + '-' + LABEL_m + verb,
            'w',
        )
        for i in range(0, len(r_out)):

            f.writelines(' '.join([str(tmp) for tmp in S_out[i]]) + '\n')

        f.close()

    filename = (setting.algorithm + '-' + setting.prime + '-' + setting.style +
                '-' + setting.formula + '-' + LABEL_m + verb)
    return attrdict(name='csidh-strategy', **locals())
Beispiel #17
0
class known_df_p512(object):

    """
    Known-answer tests

    We currently only have these for p512 df keys.

    These keys and answers came from running csidh/main.py (which generated
    these keys) prior to the big refactoring.

    To run this test for just one formula, you can run commands like this:
        pytest -k 'known_df and hvelu'
    """

    keys = attrdict(
        A=attrdict(
            sk=[
                # fmt: off
                9, 2, -14, 3, 11, -12, 4, 4, 0, 20, 8, 7, -12, -20, 23, 15, 5,
                3, 15, -19, 7, -17, -19, 1, -2, 14, 0, -9, 2, 4, 11, 2, 7, 9,
                9, -1, 5, -7, 5, 4, -4, 6, 6, 7, -8, -2, 0, 2, -6, 5, -2, 0,
                -2, 4, -5, -1, -5, 3, 3, 5, 3, -5, 5, 3, -2, 2, -4, -2, 0, -2,
                2, 0, 2, -3,
                # fmt: on
            ],
            pk=[
                0x2B84079DFE34DACA1000B23DE50ED8E581C2F8F174FDBCB03D3CA6C96361E731152F45BDD4959832DE406E8F3C4F0B4949C4826AF82B3A7362677A458196FBCF,
                0x76599305D04FB32F8907B2279EE35BE99786DA7C055E4A922712A6B546D457FA8DB9529049BBE500BE47E23DAE04ECD34E043264A02BB1917DFDF9FA540F233,
            ],
            compressed=0x27AD85DDC08BF510F08A8562BA4909803675536A0BCE6250E3BED4A9401AC123FE75C18866625E9FCFCAF03D0927ED46665E153E786244DAAAC9A83075060C82,
        ),
        B=attrdict(
            sk=[
                # fmt: off
                3, -16, -10, 1, 15, 20, -20, -22, -16, -22, 0, -19, 6, -4, -9,
                13, -11, 13, -13, -1, 23, 21, -5, 13, -4, -2, 12, 15, -4, -10,
                -5, 0, 11, 1, -1, -1, 7, 1, -3, 6, 0, 2, -4, -5, 0, 2, -4, -2,
                -4, -5, 6, 2, -6, -4, 5, -5, 5, -3, 1, 3, -1, -5, 3, -5, -4, 2,
                4, 2, 2, 4, 0, -2, 0, -3,
                # fmt: on
            ],
            pk=[
                0x5E2FD48334E49B47BB754F88B3345C77D604EB1FADC29B35B931724459143ABDE2F22346B595E3B161D80F3659870F16D4983BFA58F5F2F9718D3B375C21D65C,
                0x314B346A927C21051052B790809D895627ED8FBE4F008408D361223A97556EC0E6D3B544B0898DAFFCDBFF5C5B409CCB5CC9E2EDC95504FCA54318071E28E054,
            ],
            compressed=0x181C39753CCB4D3358E32B4471EE73EDC568846CA3B0B17571A09BD7373B4658251ADF466FF1FFB29D89B382184703C708F71497611A4B643BD984D847F3A430,
        ),
    )

    ss = 0x1ADB783878BA330BB2A842E7F8B3392329A2CD3B407900E4CF6A8F13B744BFFEFF617BDE2CEBBB9CE97D32BC6FC1BCE2D88381B03B3E13CFF0651EEA82D02937

    prime = 'p512'
    formula = NotImplemented
    style = 'df'
    tuned = False
    exponent = 2
    multievaluation = False
    verbose = False

    def setUp(self):
        self.csidh = CSIDH(
            'montgomery',
            self.prime,
            self.formula,
            self.style,
            self.tuned,
            self.exponent,
            self.multievaluation,
            self.verbose,
        )

        self.coeff = self.csidh.curve.coeff

    def test_dh_AB(self):
        self.assertEqual(
            self.coeff(self.csidh.gae.dh(self.keys.A.sk, self.keys.B.pk)),
            self.ss,
        )

    def test_dh_BA(self):
        self.assertEqual(
            self.coeff(self.csidh.gae.dh(self.keys.B.sk, self.keys.A.pk)),
            self.ss,
        )

    def test_compress(self):
        for keys in self.keys.values():
            self.assertEqual(keys.compressed, self.csidh.curve.coeff(keys.pk))

    def test_compress_roundtrip(self):
        compress, uncompress = (
            self.csidh.curve.coeff,
            self.csidh.curve.affine_to_projective,
        )
        for keys in self.keys.values():
            self.assertEqual(
                keys.compressed, compress(uncompress(compress(keys.pk)))
            )
Beispiel #18
0
def Gae(prime, tuned, curve, formula):
    '''
    >>> from sidh.bsidh.strategy import Gae
    >>> from sidh.bsidh.montgomery import MontgomeryCurve
    >>> from sidh.bsidh.hvelu import Hvelu
    >>> import pdb
    >>> prime = 'b2'
    >>> tuned = False
    >>> multievaluation = False
    >>> curve = MontgomeryCurve(prime)
    >>> formula = Hvelu(curve, tuned, multievaluation)
    >>> gae = Gae(prime, tuned, curve, formula)
    >>> sk_a = 0x642DCCC20D71FAFDFBA18D94E19777E8601494E718CB04E330C4BFE0181C209
    >>> sk_b = 0x135EB57C05FD58E80531C7CDDE36F2A7BBA88C55E8A70A0A97917D554AFA1EB6
    >>> pk_a = gae.pubkey_A(sk_a)
    >>> a_curve = curve.coeff(pk_a)
    >>> pk_b = gae.pubkey_B(sk_b)
    >>> b_curve = curve.coeff(pk_b)
    >>> ss_a = gae.dh_A(sk_a, pk_b)
    >>> curve_ss_a = curve.coeff(ss_a)
    >>> ss_b = gae.dh_B(sk_b, pk_a)
    >>> curve_ss_b = curve.coeff(ss_b)
    >>> assert curve_ss_a == curve_ss_b
    '''
    fp = curve.fp
    global_L = curve.L
    n = curve.n
    m = curve.p
    curve = curve
    random = SystemRandom()
    tuned_name = ('-classical', '-suitable')[tuned]
    SQR, ADD = curve.SQR, curve.ADD

    SIDp = curve.SIDp
    SIDm = curve.SIDm

    # Reading public generators points
    f = open(resource_filename('sidh', 'data/gen/' + prime))

    # x(PA), x(QA) and x(PA - QA)
    PQA = f.readline()
    PQA = [int(x, 16) for x in PQA.split()]
    PA = [list(PQA[0:2]), [0x1, 0x0]]
    QA = [list(PQA[2:4]), [0x1, 0x0]]
    PQA = [list(PQA[4:6]), [0x1, 0x0]]

    # x(PB), x(QB) and x(PB - QB)
    PQB = f.readline()
    PQB = [int(x, 16) for x in PQB.split()]
    PB = [list(PQB[0:2]), [0x1, 0x0]]
    QB = [list(PQB[2:4]), [0x1, 0x0]]
    PQB = [list(PQB[4:6]), [0x1, 0x0]]

    f.close()

    # These are for nonlocal in the pk/dh functions
    PA_b, QA_b, PQA_b = None, None, None
    PB_a, QB_a, PQB_a = None, None, None

    A = [[0x8, 0x0], [0x4, 0x0]]
    a = curve.coeff(A)

    # random_key() implements an uniform random integer sample [this functions should be modified]
    random = SystemRandom()
    random_key_A = lambda m=m: random.randint(0, m + 1)
    random_key_B = lambda m=m: random.randint(0, m - 1)
    random_key = lambda m=m: random.randint(0, m)

    # In order to achieve efficiency, the optimal strategies and their cost are saved in two global dictionaries (hash tables)
    S = {1: {}}  # Initialization of each strategy
    C = {1: {}}  # Initialization of the costs: 0.

    for i in range(n):
        j = global_L.index(curve.SID[i])
        S[1][tuple([global_L[j]])] = []
        # Strategy with a list with only one element (a small odd prime number l_i)
        C[1][tuple([
            global_L[j]
        ])] = formula.C_xISOG[j]  # Degree-l_i isogeny construction cost

    for i in range(2, n + 1):

        C[i] = {}
        S[i] = {}

    def dynamic_programming_algorithm(L, n):
        '''
        dynamic_programming_algorithm():
        inputs: the list of small odd primes to be processed and its length
        output: the optimal strategy and its cost of the input list of small odd primes
        '''
        nonlocal S, C
        # If the approach uses dummy operations, to set DUMMY = 2.0;
        # otherwise, to set DUMMY = 1.0 (dummy free approach);

        if len(L) != n:

            # If the list of prime numbers doesn't have size n, then we return [],-1
            print(
                "error:\tthe list of prime numbers has different size from %d."
                % n)
            return [], -1
        else:

            # Assuming #L = n, we proceed.
            get_neighboring_sets = lambda L, k: [
                tuple(L[i:i + k]) for i in range(n - k + 1)
            ]  # This function computes all the k-tuple: (l_1, l_2, ..., l_{k)),
            # (l_2, l_3, ..., l_{k+1)), ..., (l_{n-k}, l_{n-k+1, ..., l_{n)).
            for i in range(2, n + 1):

                for Tuple in get_neighboring_sets(L, i):

                    if C[i].get(Tuple) is None:

                        alpha = [
                            (
                                b,
                                C[len(Tuple[:b])][Tuple[:b]] +
                                C[  # Subtriangle on the left side with b leaves
                                    len(Tuple[b:])][Tuple[b:]] +
                                1.0  # Subtriangle on the right side with (i - b) leaves
                                * sum([
                                    curve.C_xMUL[global_L.index(t)]
                                    for t in Tuple[:b]
                                ]) +
                                1.0  # Weights corresponding with vertical edges required for connecting the vertex (0,0) with the subtriangle with b leaves
                                * sum([
                                    formula.C_xEVAL[global_L.index(t)]
                                    for t in Tuple[b:]
                                ]),  # Weights corresponding with horizontal edges required for connecting the vertex (0,0) with the subtriangle with (i - b) leaves
                            ) for b in range(1, i)
                        ]
                        b, C[i][Tuple] = min(
                            alpha, key=lambda t: curve.measure(t[1])
                        )  # We save the minimal cost corresponding to the triangle with leaves Tuple
                        S[i][Tuple] = (
                            [b] + S[i - b][Tuple[b:]] + S[b][Tuple[:b]]
                        )  # We save the optimal strategy corresponding to the triangle with leaves Tuple

            return S[n][tuple(L)], C[n][tuple(L)]  #

    def evaluate_strategy(EVAL, S_in, T_in, ST_in, E, P, L, strategy, n):
        '''
        evaluate_strategy():
                 primes;
        output : the projective Montgomery constants a24:= a + 2c and c24:=4c where E': y^2 = x^3 + (a/c)*x^2 + x
                     is E / <P>
        '''

        ramifications = []
        moves = [
            0
        ]  # moves: this list determines whether an isogeny construction must be performed
        k = 0  # k: current element of the strategy

        ramifications.append(list(P))
        E_i = list(E)

        if EVAL:
            # Public points to be evaluated
            S_out = list(
                S_in
            )  # x(S) should be a torsion point with not common factors in L
            T_out = list(
                T_in
            )  # x(T) should be a torsion point with not common factors in L
            ST_out = list(
                ST_in
            )  # x(S - T) should be a torsion point with not common factors in L
        else:
            S_out = None
            T_out = None
            ST_out = None

        assert len(strategy) == (n - 1)
        for i in range(len(strategy)):

            pos = global_L.index(
                L[n - 1 - i])  # Current element of global_L to be required

            # Reaching the vertex (n - 1 - i, i)
            # Vertical edges (scalar multiplications)
            prev = sum(moves)
            while prev < (n - 1 - i):

                moves.append(
                    strategy[k])  # Number of vertical edges to be performed
                T = list(ramifications[-1])  # New ramification
                for j in range(prev, prev + strategy[k], 1):
                    T = curve.xMUL(T, E_i, global_L.index(L[j]))

                ramifications.append(list(T))
                prev += strategy[k]
                k += 1

            # Deciding which velu variant will be used
            if formula.name != 'tvelu':
                # This branchs corresponds with the use of the new velu's formulaes

                if tuned:
                    formula.set_parameters_velu(formula.sJ_list[pos],
                                                formula.sI_list[pos], pos)

                else:
                    # -------------------------------------------------------------
                    # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                    # These paramters are required in formula.KPs, formula.xISOG, and formula.xEVAL
                    if global_L[pos] <= 4:
                        b = 0
                        c = 0
                    else:
                        b = int(floor(sqrt(global_L[pos] - 1) / 2.0))
                        c = int(floor((global_L[pos] - 1.0) / (4.0 * b)))

                    if formula.name != 'tvelu':
                        formula.set_parameters_velu(b, c, pos)

            # Kernel Points computation
            formula.KPs(ramifications[-1], E_i, pos)

            # Isogeny construction
            ramifications[-1][0], E_i[0] = fp.fp2_cswap(
                ramifications[-1][0], E_i[0], global_L[pos] == 4)
            ramifications[-1][1], E_i[1] = fp.fp2_cswap(
                ramifications[-1][1], E_i[1], global_L[pos] == 4)
            C_i = formula.xISOG(E_i, pos)
            ramifications[-1][0], E_i[0] = fp.fp2_cswap(
                ramifications[-1][0], E_i[0], global_L[pos] == 4)
            ramifications[-1][1], E_i[1] = fp.fp2_cswap(
                ramifications[-1][1], E_i[1], global_L[pos] == 4)

            # Now, we proceed by perform horizontal edges (isogeny evaluations)
            for j in range(0, len(moves) - 1, 1):

                if (formula.name == 'tvelu'
                        or (formula.name == 'hvelu'
                            and global_L[pos] <= formula.HYBRID_BOUND)
                        or (global_L[pos] == 4)):
                    ramifications[j] = formula.xEVAL(ramifications[j], pos)
                else:
                    ramifications[j] = formula.xEVAL(ramifications[j], E_i)

            if EVAL:
                # Evaluating public points
                if (formula.name == 'tvelu'
                        or (formula.name == 'hvelu'
                            and global_L[pos] <= formula.HYBRID_BOUND)
                        or (global_L[pos] == 4)):

                    S_out = formula.xEVAL(S_out, pos)
                    T_out = formula.xEVAL(T_out, pos)
                    ST_out = formula.xEVAL(ST_out, pos)
                else:

                    S_out = formula.xEVAL(S_out, E_i)
                    T_out = formula.xEVAL(T_out, E_i)
                    ST_out = formula.xEVAL(ST_out, E_i)

            # Updating the Montogmery curve coefficients
            E_i = [list(C_i[0]), list(C_i[1])]

            moves.pop()
            ramifications.pop()

        pos = global_L.index(
            L[0])  # Current element of global_L to be required

        if formula.name != 'tvelu':
            # This branchs corresponds with the use of the new velu's formulaes

            if tuned:
                formula.set_parameters_velu(formula.sJ_list[pos],
                                            formula.sI_list[pos], pos)

            else:
                # -------------------------------------------------------------
                # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                # These paramters are required in formula.KPs, formula.xISOG, and formula.xEVAL
                if global_L[pos] <= 4:
                    b = 0
                    c = 0
                else:
                    b = int(floor(sqrt(global_L[pos] - 1) / 2.0))
                    c = int(floor((global_L[pos] - 1.0) / (4.0 * b)))

                formula.set_parameters_velu(b, c, pos)

        # Kernel Points computations
        formula.KPs(ramifications[0], E_i, pos)

        # Isogeny construction
        ramifications[0][0], E_i[0] = fp.fp2_cswap(ramifications[0][0], E_i[0],
                                                   global_L[pos] == 4)
        ramifications[0][1], E_i[1] = fp.fp2_cswap(ramifications[0][1], E_i[1],
                                                   global_L[pos] == 4)
        C_i = formula.xISOG(E_i, pos)
        ramifications[0][0], E_i[0] = fp.fp2_cswap(ramifications[0][0], E_i[0],
                                                   global_L[pos] == 4)
        ramifications[0][1], E_i[1] = fp.fp2_cswap(ramifications[0][1], E_i[1],
                                                   global_L[pos] == 4)

        if EVAL:
            # Evaluating public points
            if (formula.name == 'tvelu'
                    or (formula.name == 'hvelu'
                        and global_L[pos] <= formula.HYBRID_BOUND)
                    or (global_L[pos] == 4)):

                S_out = formula.xEVAL(S_out, pos)
                T_out = formula.xEVAL(T_out, pos)
                ST_out = formula.xEVAL(ST_out, pos)

            else:

                S_out = formula.xEVAL(S_out, E_i)
                T_out = formula.xEVAL(T_out, E_i)
                ST_out = formula.xEVAL(ST_out, E_i)

        # Updating the Montogmery curve coefficients
        E_i = [list(C_i[0]), list(C_i[1])]

        return E_i, S_out, T_out, ST_out

    def pubkey_A(sk_a):
        nonlocal PB_a, QB_a, PQB_a
        Ra = curve.Ladder3pt(sk_a, PA, QA, PQA, A)
        pk_a, PB_a, QB_a, PQB_a = evaluate_strategy(True, PB, QB, PQB, A, Ra,
                                                    curve.SIDp[::-1], Sp,
                                                    len(curve.SIDp))
        #a_curve = curve.coeff(pk_a)
        return pk_a

    def pubkey_B(sk_b):
        nonlocal PA_b, QA_b, PQA_b
        Rb = curve.Ladder3pt(sk_b, PB, QB, PQB, A)
        pk_b, PA_b, QA_b, PQA_b = evaluate_strategy(True, PA, QA, PQA, A, Rb,
                                                    curve.SIDm[::-1], Sm,
                                                    len(curve.SIDm))
        #b_curve = curve.coeff(pk_b)
        return pk_b

    def dh_A(sk_a, pk_b):
        # sk here is alice's secret key
        # pk_b here is from bob (not processed by coeff)
        nonlocal PA_b, QA_b, PQA_b
        RB_a = curve.Ladder3pt(sk_a, PA_b, QA_b, PQA_b, pk_b)
        ss_a, _, _, _ = evaluate_strategy(False, PB, QB, PQB, pk_b,
                                          RB_a, curve.SIDp[::-1], Sp,
                                          len(curve.SIDp))
        #ss_a_curve = curve.coeff(ss_a)
        #return ss_a_curve
        return ss_a

    def dh_B(sk_b, pk_a):
        # sk_b here is bob's secret key
        # pk_a here is from alice (not processed by coeff)
        nonlocal PB_a, QB_a, PQB_a
        RA_b = curve.Ladder3pt(sk_b, PB_a, QB_a, PQB_a, pk_a)
        ss_b, _, _, _ = evaluate_strategy(False, PA, QA, PQA, pk_a,
                                          RA_b, curve.SIDm[::-1], Sm,
                                          len(curve.SIDm))
        #ss_b_curve = curve.coeff(ss_b)
        #return ss_b_curve
        return ss_b

    f_name = 'data/strategies/bsidh-' + prime + '-' + formula.name + tuned_name
    try:
        f = open(resource_filename('sidh', f_name))
        # Corresponding to the list of Small Isogeny Degree, Lp := [l_0, ...,
        # l_{n-1}] [We need to include case l=2 and l=4]
        tmp = f.readline()
        tmp = [int(b) for b in tmp.split()]
        Sp = list(tmp)
        # Corresponding to the list of Small Isogeny Degree, Lm := [l_0, ...,
        # l_{n-1}]
        tmp = f.readline()
        tmp = [int(b) for b in tmp.split()]
        Sm = list(tmp)
        f.close()
    except IOError:
        print("// Strategies to be computed")
        # List of Small Isogeny Degree, Lp := [l_0, ..., l_{n-1}] [We need to
        # include case l=2 and l=4]
        Sp, Cp = dynamic_programming_algorithm(SIDp[::-1], len(SIDp))
        # List of Small Isogeny Degree, Lm := [l_0, ..., l_{n-1}]
        Sm, Cm = dynamic_programming_algorithm(SIDm[::-1], len(SIDm))
        f = open(f_name, 'w')
        f.writelines(' '.join([str(tmp) for tmp in Sp]) + '\n')
        f.writelines(' '.join([str(tmp) for tmp in Sm]) + '\n')
        f.close()
    #print(
    #  "// All the experiments are assuming S = %1.6f x M and a = %1.6f x M. The curve.measures are given in millions of field operations.\n"
    #  % (SQR, ADD)
    #)

    return attrdict(locals())
Beispiel #19
0
def csidh_suitable_bounds(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    L = algo.params.L
    n = algo.params.n
    strategy_block_cost = algo.gae.strategy_block_cost
    measure = algo.curve.measure
    security = algo.gae.security
    basis = algo.gae.basis

    print_intv = lambda v, n: ', '.join(list(map(format, v, ['2d'] * n)))

    # The next function computes the set \mu() given in the paper
    def neighboring_intvec(seq_i, L, IN, OUT):
        if OUT[2] >= 256.00:
            return OUT

        else:
            minimum = IN
            if measure(IN[1]) >= measure(OUT[1]):

                for j in seq_i:

                    current_cost, _, _, _, _ = strategy_block_cost(
                        L, OUT[0] + basis[j])
                    tmp = neighboring_intvec(
                        seq_i,
                        L,
                        IN,
                        (
                            OUT[0] + basis[j],
                            current_cost,
                            security(OUT[0] + basis[j], len(L)),
                        ),
                    )
                    if measure(minimum[1]) >= measure(tmp[1]):
                        minimum = tmp

            return minimum

    # Finally, the next functions is the implementation of algorithm 2.0
    def optimal_bounds(L, b, r):

        n = len(L)

        RNC, _, _, _, _ = strategy_block_cost(L, b)
        SEC = security(b, n)
        e = b

        # for i in range(n-1,-1,-1):
        for i in range(n):

            # The algorithm proceed by looking the best bounds when e_i <- e_i - 1
            seq_i = [k for k in range(n) if k != i]
            (e_tmp, RNC_tmp, SEC_tmp) = (e, RNC, SEC)
            if e[i] > r:

                # Set the new possible optimal bounds
                temporal_cost, _, _, _, _ = strategy_block_cost(
                    L, e - r * basis[i])
                (e_tmp, RNC_tmp, SEC_tmp) = neighboring_intvec(
                    seq_i,
                    L,
                    (e, RNC, SEC),
                    (
                        e - r * basis[i],
                        temporal_cost,
                        security(e - r * basis[i], n),
                    ),
                )

            (e, RNC, SEC) = min(
                [(e_tmp, RNC_tmp, SEC_tmp), (e, RNC, SEC)],
                key=lambda t: measure(t[1]),
            )
            # --------------------------------------------------------------------------------------------------
            f = open(tmp_dir + setting.style + ".out", "a")
            f.write("decreasing: e_{" + print_intv([i], 1) + "}" +
                    ", and increasing each e_j with j != " +
                    print_intv([i], 1) +
                    "; current optimal running-time: %7.3f\n" % measure(RNC))
            f.write("[" + print_intv(e, n) + "]\n")
            f.close()
            # --------------------------------------------------------------------------------------------------

            print("decreasing: e_{" + print_intv([i], 1) + "}" +
                  ", and increasing each e_j with j != " + print_intv([i], 1) +
                  "; current optimal running-time: %7.3f" % measure(RNC))
            print("[" + print_intv(e, n) + "]\n")

        print("Security in bits ~ %f\n" % SEC)
        return (e, RNC)

    ''' -------------------------------------------------------------------------------------
        Number of degree-(l_i) isogeny constructions to be performed: m_i
        ------------------------------------------------------------------------------------- '''

    # ==========================================================================

    if setting.prime == 'p512':

        if setting.style == 'wd1':
            # __________________________________________________________________________
            # ====== [MCR style, CSIDH-512]
            m = 10

        if setting.style == 'wd2':
            # __________________________________________________________________________
            # ====== [OAYT style, CSIDH-512]
            m = 5

        if setting.style == 'df':
            # __________________________________________________________________________
            # ====== [dummy-free style, CSIDH-512]
            m = 10

    if setting.prime == 'p1024':

        if setting.style == 'wd1':
            # __________________________________________________________________________
            # ====== [MCR style, CSIDH-1024]
            m = 3

        if setting.style == 'wd2':
            # __________________________________________________________________________
            # ====== [OAYT style, CSIDH-1024]
            m = 2

        if setting.style == 'df':
            # __________________________________________________________________________
            # ====== [dummy-free style, CSIDH-1024]
            m = 3

    if setting.prime == 'p1792':

        if setting.style == 'wd1':
            # __________________________________________________________________________
            # ====== [MCR style, CSIDH-1024]
            m = 2

        if setting.style == 'wd2':
            # __________________________________________________________________________
            # ====== [OAYT style, CSIDH-1024]
            m = 1

        if setting.style == 'df':
            # __________________________________________________________________________
            # ====== [dummy-free style, CSIDH-1024]
            m = 2

    # ---
    k = 3
    # Next integer vector bount is given in Onuki et al. manuscript
    print(
        "\n_______________________________________________________________________________________________________________________________"
    )
    print("List of small odd primes")
    printl("L", L[::-1], n // k)
    print("\nInitial integer vector of bounts (b_0, ..., b_%d)" % n)

    e = [m] * (n - 1) + [(3 * m) // 2]
    e = numpy.array(e)
    printl("e", e, n // k)
    RUNNING_TIME, _, _, _, _ = strategy_block_cost(L[::-1], e)

    print(
        "// Number of field operations (GAE):\t%1.6f x M + %1.6f x S + %1.6f x a := %1.6f x M"
        % (
            RUNNING_TIME[0] / (10.0**6),
            RUNNING_TIME[1] / (10.0**6),
            RUNNING_TIME[2] / (10.0**6),
            measure(RUNNING_TIME) / (10.0**6),
        ))
    print("\tSecurity ~ %f\n" % security(e, n))

    print(
        "_______________________________________________________________________________________________________________________________"
    )
    print("We proceed by searching a better integer vector of bounds\n")
    f = open(tmp_dir + setting.style + ".out", "w")
    f.write("\n")
    f.close()

    r = 1
    for k in range(1, int(ceil((1.0 * m) / (1.0 * r)))):

        e, RNC = optimal_bounds(L[::-1], e, r)
        f = open(tmp_dir + setting.style + ".out", "a")
        f.write("\n")
        f.close()

    print(
        "_______________________________________________________________________________________________________________________________\n"
    )
    return attrdict(name='csidh-suitable-bounds', **locals())
Beispiel #20
0
def Tvelu(curve):

    global_L = curve.global_L
    prime = curve.prime
    fp = curve.fp
    p = curve.p
    np = curve.np
    nm = curve.nm
    Em = curve.Em
    Ep = curve.Ep
    random = SystemRandom()
    poly_mul = Poly_mul(curve)
    poly_redc = Poly_redc(poly_mul)

    cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)])
    cISOG = lambda l: numpy.array([
        (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0),
        (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0),
        (3.0 * l - 7.0 + isequal[l == 3] * 6.0),
    ])

    C_xEVAL = list(
        map(cEVAL,
            global_L))  # list of the costs of each degree-l isogeny evaluation
    C_xISOG = list(map(
        cISOG,
        global_L))  # list of the costs of each degree-l isogeny construction

    K = None

    def yDBL(P, A):
        '''
        -------------------------------------------------------------------------
        yDBL()
        input : a projective Twisted Edwards y-coordinate point y(P) := YP/WP,
                and the projective Montgomery constants A24:= A + 2C and C24:=4C
                where E : y^2 = x^3 + (A/C)*x^2 + x
        output: the projective Twisted Edwards y-coordinate point y([2]P)
        -------------------------------------------------------------------------
        '''
        t_0 = fp.fp2_sqr(P[0])
        t_1 = fp.fp2_sqr(P[1])
        Z = fp.fp2_mul(A[1], t_0)
        X = fp.fp2_mul(Z, t_1)
        t_1 = fp.fp2_sub(t_1, t_0)
        t_0 = fp.fp2_mul(A[0], t_1)
        Z = fp.fp2_add(Z, t_0)
        Z = fp.fp2_mul(Z, t_1)
        return [fp.fp2_sub(X, Z), fp.fp2_add(X, Z)]

    def yADD(P, Q, PQ):
        '''
        -------------------------------------------------------------------------
        yADD()
        input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP,
                y(Q) := YQ/WQ, and y(P-Q) := YPQ/QPQ
        output: the projective Twisted Edwards y-coordinate point y(P+Q)
        -------------------------------------------------------------------------
        '''
        a = fp.fp2_mul(P[1], Q[0])
        b = fp.fp2_mul(P[0], Q[1])
        c = fp.fp2_add(a, b)
        d = fp.fp2_sub(a, b)
        c = fp.fp2_sqr(c)
        d = fp.fp2_sqr(d)

        xD = fp.fp2_add(PQ[1], PQ[0])
        zD = fp.fp2_sub(PQ[1], PQ[0])
        X = fp.fp2_mul(zD, c)
        Z = fp.fp2_mul(xD, d)
        return [fp.fp2_sub(X, Z), fp.fp2_add(X, Z)]

    def KPs_t(P, A, i):
        '''
        -------------------------------------------------------------------------
        KPs()
        input : the projective Twisted Edwards y-coordinate points y(P) := YP/WP,
                the projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, and a positive integer 0 <= i < n
        output: the list of projective Twisted Edwards y-coordinate points y(P),
                y([2]P), y([3]P), ..., and y([d_i]P) where l_i = 2 * d_i + 1
        -------------------------------------------------------------------------
        '''
        assert curve.isinfinity(P) == False
        nonlocal K
        d = (global_L[i] - 1) // 2

        K = [[[0, 0], [0, 0]] for j in range(d + 1)]
        K[0] = list([fp.fp2_sub(P[0], P[1]), fp.fp2_add(P[0], P[1])])  # 2a
        if global_L[i] == 3:
            K[1] = list([list(K[0]), list(K[1])])
        else:
            K[1] = yDBL(K[0], A)  # 4M + 2S + 4a

        for j in range(2, d, 1):
            K[j] = yADD(K[j - 1], K[0], K[j - 2])  # 4M + 2S + 6a

        return K  # 2(l - 3)M + (l - 3)S + 3(l - 3)a

    def xISOG_t(A, i):
        '''
        ------------------------------------------------------------------------------
        xISOG()
        input : the projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted
                Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P)
                where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n
        output: the projective Montgomery constants a24:= a + 2c and c24:=4c where
                E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E
        ------------------------------------------------------------------------------
        '''
        nonlocal K
        l = global_L[i]  # l
        bits_of_l = bitlength(l)  # Number of bits of L[i]
        d = (l - 1) // 2  # Here, l = 2d + 1

        By = K[0][0]
        Bz = K[0][1]
        for j in range(1, d, 1):

            By = fp.fp2_mul(By, K[j][0])
            Bz = fp.fp2_mul(Bz, K[j][1])

        bits_of_l -= 1
        constant_d_edwards = fp.fp2_sub(A[0], A[1])  # 1a

        tmp_a = A[0]
        tmp_d = constant_d_edwards
        # left-to-right method for computing a^l and d^l
        for j in range(1, bits_of_l + 1):

            tmp_a = fp.fp2_sqr(tmp_a)
            tmp_d = fp.fp2_sqr(tmp_d)
            if ((l >> (bits_of_l - j)) & 1) != 0:

                tmp_a = fp.fp2_mul(tmp_a, A[0])
                tmp_d = fp.fp2_mul(tmp_d, constant_d_edwards)

        for j in range(3):

            By = fp.fp2_sqr(By)
            Bz = fp.fp2_sqr(Bz)

        C0 = fp.fp2_mul(tmp_a, Bz)
        C1 = fp.fp2_mul(tmp_d, By)
        C1 = fp.fp2_sub(C0, C1)

        return [C0, C1]  # (l - 1 + 2*HW(l) - 2)M + 2(|l|_2 + 1)S + 2a

    def xEVAL_t(P, i):
        '''
        ------------------------------------------------------------------------------
        xEVAL()
        input : the projective Montgomery constants A24:= A + 2C and C24:=4C where
                E : y^2 = x^3 + (A/C)*x^2 + x, the list of projective Twisted
                Edwards y-coordinate points y(P), y([2]P), y([3]P), ..., and y([d_i]P)
                where l_i = 2 * d_i + 1, and a positive integer 0 <= i < n
        output: the projective Montgomery constants a24:= a + 2c and c24:=4c where
                E': y^2 = x^3 + (a/c)*x^2 + x is a degree-(l_i) isogenous curve to E
        ------------------------------------------------------------------------------
        '''
        nonlocal K
        d = (global_L[i] - 1) // 2  # Here, l = 2d + 1

        Q0 = fp.fp2_add(P[0], P[1])
        Q1 = fp.fp2_sub(P[0], P[1])
        R0, R1 = curve.CrissCross(K[0][1], K[0][0], Q0, Q1)
        for j in range(1, d, 1):

            T0, T1 = curve.CrissCross(K[j][1], K[j][0], Q0, Q1)
            R0 = fp.fp2_mul(T0, R0)
            R1 = fp.fp2_mul(T1, R1)

        R0 = fp.fp2_sqr(R0)
        R1 = fp.fp2_sqr(R1)
        X = fp.fp2_mul(P[0], R0)
        Z = fp.fp2_mul(P[1], R1)

        return [X, Z]  # 2(l - 1)M + 2S + (l + 1)a

    def xISOG_4(P):
        """
        Degree-4 isogeny construction
        """
        nonlocal K
        K = [[0, 0], [0, 0], [0, 0]]

        K[1] = fp.fp2_sub(P[0], P[1])
        K[2] = fp.fp2_add(P[0], P[1])
        K[0] = fp.fp2_sqr(P[1])
        K[0] = fp.fp2_add(K[0], K[0])

        C24 = fp.fp2_sqr(K[0])
        K[0] = fp.fp2_add(K[0], K[0])
        A24 = fp.fp2_sqr(P[0])
        A24 = fp.fp2_add(A24, A24)
        A24 = fp.fp2_sqr(A24)
        return [A24, C24]

    def xEVAL_4(Q):
        """
        Degree-4 isogeny evaluation
        """
        t0 = fp.fp2_add(Q[0], Q[1])
        t1 = fp.fp2_sub(Q[0], Q[1])
        XQ = fp.fp2_mul(t0, K[1])
        ZQ = fp.fp2_mul(t1, K[2])
        t0 = fp.fp2_mul(t0, t1)
        t0 = fp.fp2_mul(t0, K[0])
        t1 = fp.fp2_add(XQ, ZQ)
        ZQ = fp.fp2_sub(XQ, ZQ)
        t1 = fp.fp2_sqr(t1)
        ZQ = fp.fp2_sqr(ZQ)
        XQ = fp.fp2_add(t0, t1)
        t0 = fp.fp2_sub(ZQ, t0)
        XQ = fp.fp2_mul(XQ, t1)
        ZQ = fp.fp2_mul(ZQ, t0)

        return [XQ, ZQ]

    def KPs(P, A, i):
        if global_L[i] != 4:
            return KPs_t(P, A, i)

    def xISOG(A, i):
        if global_L[i] != 4:
            return xISOG_t(A, i)
        else:
            # A should corresponds with an order-4 point
            return xISOG_4(A)

    def xEVAL(P, i):
        if global_L[i] != 4:
            return xEVAL_t(P, i)
        else:
            return xEVAL_4(P)

    def cISOG_and_cEVAL():
        """
        Get cost of the isogeny constructions and evaluations
        """
        nonlocal C_xISOG
        nonlocal C_xEVAL

        # E[p + 1]
        # First, we look for a full torsion point
        A = [[0x8, 0x0], [0x4, 0x0]]

        # Reading public generators points
        f = open(resource_filename('sidh', 'data/gen/' + prime))
        # x(PA), x(QA) and x(PA - QA)
        PQA = f.readline()
        PQA = [int(x, 16) for x in PQA.split()]
        PA = [list(PQA[0:2]), [0x1, 0x0]]
        QA = [list(PQA[2:4]), [0x1, 0x0]]
        PQA = [list(PQA[4:6]), [0x1, 0x0]]

        # x(PB), x(QB) and x(PB - QB)
        PQB = f.readline()
        PQB = [int(x, 16) for x in PQB.split()]
        PB = [list(PQB[0:2]), [0x1, 0x0]]
        QB = [list(PQB[2:4]), [0x1, 0x0]]
        PQB = [list(PQB[4:6]), [0x1, 0x0]]

        f.close()

        for i in range(0, Ep[0] - 1, 1):

            PA = curve.xMUL(PA, A, 0)
            QA = curve.xMUL(QA, A, 0)
            PQA = curve.xMUL(PQA, A, 0)

        # Random kernels for counting the
        T_p = curve.Ladder3pt(random.randint(0, p - 1), PA, QA, PQA, A)
        T_m = curve.Ladder3pt(random.randint(0, p - 1), PB, QB, PQB, A)

        for i in range(0, np, 1):
            for j in range(0, Ep[i] - 1, 1):
                T_p = curve.xMUL(T_p, A, i)

        for i in range(np, np + nm, 1):
            for j in range(0, Em[i - np] - 1, 1):
                T_m = curve.xMUL(T_m, A, i)

        for i in range(0, np, 1):

            # Getting an orderl-l point
            Tp = list(T_p)
            for j in range(i + 1, np, 1):
                Tp = curve.xMUL(Tp, A, j)

            # Cost of xISOG() and KPs()
            fp.fp.set_zero_ops()
            KPs(Tp, A, i)
            t = fp.fp.get_ops()
            C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            fp.fp.set_zero_ops()
            Tp[0], A[0] = fp.fp2_cswap(Tp[0], A[0], global_L[i] == 4)
            Tp[1], A[1] = fp.fp2_cswap(Tp[1], A[1], global_L[i] == 4)
            B = xISOG(A, i)
            Tp[0], A[0] = fp.fp2_cswap(Tp[0], A[0], global_L[i] == 4)
            Tp[1], A[1] = fp.fp2_cswap(Tp[1], A[1], global_L[i] == 4)
            t = fp.fp.get_ops()
            C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # xEVAL: kernel point determined by the next isogeny evaluation
            fp.fp.set_zero_ops()
            T_p = xEVAL(T_p, i)

            # Cost of xEVAL
            fp.fp.set_zero_ops()
            T_m = xEVAL(T_m, i)
            t = fp.fp.get_ops()
            C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # Updating the new next curve
            A = list(B)

        # E[p - 1]
        # First, we look for a full torsion point
        A = [[0x8, 0x0], [0x4, 0x0]]
        T_m = curve.Ladder3pt(random.randint(0, p - 1), PA, QA, PQA, A)
        T_p = curve.Ladder3pt(random.randint(0, p - 1), PB, QB, PQB, A)

        for i in range(np, np + nm, 1):

            # Getting an orderl-l point
            Tp = list(T_p)
            for j in range(i + 1, np + nm, 1):
                Tp = curve.xMUL(Tp, A, j)

            # Cost of xISOG() and KPs()
            fp.fp.set_zero_ops()
            KPs(Tp, A, i)
            t = fp.fp.get_ops()
            C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            fp.fp.set_zero_ops()
            B = xISOG(A, i)
            t = fp.fp.get_ops()
            C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # xEVAL: kernel point determined by the next isogeny evaluation
            fp.fp.set_zero_ops()
            T_p = xEVAL(T_p, i)

            # Cost of xEVAL
            fp.fp.set_zero_ops()
            T_m = xEVAL(T_m, i)
            t = fp.fp.get_ops()
            C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # Updating the new next curve
            A = list(B)
        fp.fp.set_zero_ops()
        return None

    # Now, we proceed to store all the correct costs
    cISOG_and_cEVAL()
    return attrdict(name='tvelu', **locals())
Beispiel #21
0
def csidh_sdacs(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    L = algo.params.L
    n = algo.params.n
    m = algo.params.m
    exponent_of_two = algo.curve.exponent_of_two
    validation_stop = algo.curve.validation_stop
    SDACS = algo.curve.SDACS

    # ---
    k = 3  # Number of rows (format of the list)
    # ---

    print("#ifndef _SDACS_%s_H_" % setting.prime)
    print("#define _SDACS_%s_H_" % setting.prime)
    print("")
    assert n == len(L)

    print(
        "#define cofactor %d\t// Exponent of 2 in the factorization of (p + 1) "
        % exponent_of_two
    )
    print("")
    print("#ifdef _MONT_C_CODE_")

    print(
        "#define UPPER_BOUND %d\t// Bits of 4 * sqrt( [p + 1] / [2^e] )"
        % validation_stop
    )
    print(
        "\n// Recall, the list of Small Odd Primes (SOPs) is stored such that l_0 < l_1 < ... < l_{n-1}"
    )

    assert n == len(SDACS)
    print(
        "\n// The list of Shortest Differential Addition Chains (SDACs) corresponding with each l_i"
    )
    printl(
        "static int LENGTHS[]", [len(sdac_i) for sdac_i in SDACS], n // k + 1
    )
    print("")
    for i in range(0, n, 1):
        if len(SDACS[i]) == 0:
            print("static char SDAC%d[1];" % i)
        else:
            print(
                'static char SDAC'
                + str(i)
                + '[] = \"'
                + ''.join([str(sdac_ij) for sdac_ij in SDACS[i]])
                + '\";'
            )

    SDACS_string = "static char *SDACs[N] = {\n\t"

    for i in range(0, n - 1, 1):
        if (i + 1) % (n // k + 1) == 0:
            SDACS_string = SDACS_string + "SDAC%d, \n\t" % (i)
        else:
            SDACS_string = SDACS_string + "SDAC%d, " % (i)

    SDACS_string = SDACS_string + "SDAC%d\n\t};" % (n - 1)
    print("")
    print(SDACS_string)
    print("#endif")
    print(
        "\n#endif /* required framework for the SDACs, which is used in CSIDH-%s */"
        % setting.prime[1:]
    )
    return attrdict(name='csidh-sdacs', **locals())
Beispiel #22
0
def Svelu(curve, tuned, multievaluation):
    fp = curve.fp
    poly_mul = Poly_mul(curve)
    poly_redc = Poly_redc(poly_mul)
    global_L = curve.L
    prime = curve.prime
    SCALED_REMAINDER_TREE = multievaluation
    n = parameters["csidh"][prime]["n"]

    cEVAL = lambda l: numpy.array([2.0 * (l - 1.0), 2.0, (l + 1.0)])
    cISOG = lambda l: numpy.array([
        (3.0 * l + 2.0 * hamming_weight(l) - 9.0 + isequal[l == 3] * 4.0),
        (l + 2.0 * bitlength(l) + 1.0 + isequal[l == 3] * 2.0),
        (3.0 * l - 7.0 + isequal[l == 3] * 6.0),
    ])

    C_xEVAL = list(
        map(cEVAL,
            global_L))  # list of the costs of each degree-l isogeny evaluation
    C_xISOG = list(map(
        cISOG,
        global_L))  # list of the costs of each degree-l isogeny construction

    # Global variables to be used in KPs, xISOG, and xEVAL

    # Here, J is a set of cardinality sJ
    J = None
    sJ = None

    # Here, ptree_I corresponds with the product tree determined by I, and I is a set of cardinality sJ
    ptree_hI = None
    sI = (None, )

    # Here, K is a set of cardinality sK
    K = None
    sK = None

    # An extra nonlocal variable which is used in xISOG and xEVAL
    XZJ4 = None

    # Next functions is used for setting the cardinalities sI, sJ, and sK
    def set_parameters_velu(b, c, i):

        nonlocal sJ
        nonlocal sI
        nonlocal sK

        assert b <= c

        # At this step, everythin is correct
        sJ = b
        sI = c
        d = ((global_L[i] - 2 - 4 * b * c - 1) // 2) + 1
        assert d >= 0
        sK = d
        return None

    def print_parameters_velu():

        print("| sI: %3d, sJ: %3d, sK: %3d |" % (sI, sJ, sK), end="")
        return None

    # KPs computes x([i]P), x([j]P), and x([k]P) for each i in I, j in J, and j in J.
    # I, J, and K are defined according to Examples 4.7 and 4.12 of https://eprint.iacr.org/2020/341
    def KPs(P, A, i):

        # Global variables to be used
        nonlocal J
        nonlocal sJ
        nonlocal ptree_hI
        nonlocal sI
        nonlocal K
        nonlocal sK

        # This functions computes all the independent data from the input of algorithm 2 of https://eprint.iacr.org/2020/341
        if sI == 0:

            # Case global_L[i] = 3 is super very special case (nothing to do)
            J = []
            ptree_hI = None
            K = [list(P)]
            # J, b, ptree_hI, c, K, d
            assert sJ == 0 and sI == 0 and sK == 1
            return None

        # We need to ensure sI is greater than or equal sJ
        assert sI >= sJ

        # Additionally, sK should be greater than or equal to zero. If that is not the case, then sJ and sI were badly chosen
        assert sK >= 0

        if sI == 1:
            # Branch corresponds with global_L[i] = 5 and global_L[i] = 7
            # Recall sJ > 0, then sJ = 1
            assert sJ == 1
            P2 = curve.xDBL(P, A)

            J = [list(P)]

            I = [list(P2)]
            hI = [list([fp.fp_sub(0, P2[0]), P2[1]])
                  ]  # we only need to negate x-coordinate of each point
            ptree_hI = poly_mul.product_tree(hI, sI)  # product tree of hI

            if not SCALED_REMAINDER_TREE:
                # Using remainder trees
                ptree_hI = poly_redc.reciprocal_tree(
                    {
                        'rpoly': [1],
                        'rdeg': 0,
                        'fpoly': [1],
                        'fdeg': 0,
                        'a': 1
                    },
                    2 * sJ + 1,
                    ptree_hI,
                    sI,
                )  # reciprocal of the root is used to compute sons' reciprocals

            else:
                # Using scaled remainder trees
                assert (2 * sJ - sI + 1) > sI
                ptree_hI['reciprocal'], ptree_hI['a'] = poly_redc.reciprocal(
                    ptree_hI['poly'][::-1], sI + 1, 2 * sJ - sI + 1)
                ptree_hI['scaled'], ptree_hI['as'] = (
                    list(ptree_hI['reciprocal'][:sI]),
                    ptree_hI['a'],
                )

            # Notice, 0 <= sK <= 1
            assert sK <= 1
            if sK == 1:
                K = [list(P2)]
            else:
                K = []

            return None

        # At this step, sI > 1
        assert sI > 1
        if sJ == 1:
            # This branch corresponds with global_L[i] = 11 and global_L[i] = 13
            Q = curve.xDBL(P, A)  # x([2]P)
            Q2 = curve.xDBL(Q, A)  # x([2]Q)

            J = [list(P)]

            I = [[0, 0]] * sI
            I[0] = list(Q)  # x(   Q)
            I[1] = curve.xADD(Q2, I[0], I[0])  # x([3]Q)
            for ii in range(2, sI, 1):
                I[ii] = curve.xADD(I[ii - 1], Q2, I[ii - 2])  # x([2**i + 1]Q)

            hI = [[fp.fp_sub(0, iP[0]), iP[1]] for iP in I
                  ]  # we only need to negate x-coordinate of each point
            ptree_hI = poly_mul.product_tree(hI, sI)  # product tree of hI

            if not SCALED_REMAINDER_TREE:
                # Using remainder trees
                ptree_hI = poly_redc.reciprocal_tree(
                    {
                        'rpoly': [1],
                        'rdeg': 0,
                        'fpoly': [1],
                        'fdeg': 0,
                        'a': 1
                    },
                    2 * sJ + 1,
                    ptree_hI,
                    sI,
                )  # reciprocal of the root is used to compute sons' reciprocals

            else:
                # Using scaled remainder trees
                assert (2 * sJ - sI + 1) <= sI
                ptree_hI['scaled'], ptree_hI['as'] = poly_redc.reciprocal(
                    ptree_hI['poly'][::-1], sI + 1, sI)
                ptree_hI['reciprocal'], ptree_hI['a'] = (
                    list(ptree_hI['scaled'][:(2 * sJ - sI + 1)]),
                    ptree_hI['as'],
                )

            # Notice, 0 <= sK <= 1
            assert sK <= 1
            if sK == 1:
                K = [list(Q)]
            else:
                K = []

            return None

        # Now, we ensure sI >= sJ > 1
        assert sJ > 1

        # In other words, we can proceed by the general case

        # ------------------------------------------------
        # Computing [j]P for each j in {1, 3, ..., 2*sJ - 1}
        J = [[0, 0]] * sJ
        J[0] = list(P)  # x(   P)
        P2 = curve.xDBL(P, A)  # x([2]P)
        J[1] = curve.xADD(P2, J[0], J[0])  # x([3]P)
        for jj in range(2, sJ, 1):
            J[jj] = curve.xADD(J[jj - 1], P2, J[jj - 2])  # x([2*jj + 1]P)

        # -------------------------------------------------------
        # Computing [i]P for i in { (2*sJ) * (2i + 1) : 0 <= i < sI}
        bhalf_floor = sJ // 2
        bhalf_ceil = sJ - bhalf_floor
        P4 = curve.xDBL(P2, A)  # x([4]P)
        P2[0], P4[0] = fp.fp_cswap(P2[0], P4[0], sJ % 2)  # Constant-time swap
        P2[1], P4[1] = fp.fp_cswap(
            P2[1], P4[1], sJ % 2)  # x([4]P) <--- coditional swap ---> x([2]P)
        Q = curve.xADD(J[bhalf_ceil], J[bhalf_floor - 1], P2)  # Q := [2b]P
        P2[0], P4[0] = fp.fp_cswap(P2[0], P4[0], sJ % 2)  # Constant-time swap
        P2[1], P4[1] = fp.fp_cswap(
            P2[1], P4[1], sJ % 2)  # x([4]P) <--- coditional swap ---> x([2]P)

        I = [[0, 0]] * sI
        I[0] = list(Q)  # x(   Q)
        Q2 = curve.xDBL(Q, A)  # x([2]Q)
        I[1] = curve.xADD(Q2, I[0], I[0])  # x([3]Q)
        for ii in range(2, sI, 1):
            I[ii] = curve.xADD(I[ii - 1], Q2, I[ii - 2])  # x([2**i + 1]Q)

        # --------------------------------------------------------------
        # Computing [k]P for k in { 4*sJ*sI + 1, ..., l - 6, l - 4, l - 2}
        K = [[0, 0]] * sK

        if sK >= 1:
            K[0] = list(P2)  # x([l - 2]P) = x([-2]P) = x([2]P)

        if sK >= 2:
            K[1] = list(P4)  # x([l - 4]P) = x([-4]P) = x([4]P)

        for k in range(2, sK, 1):
            K[k] = curve.xADD(K[k - 1], P2, K[k - 2])

        # ------------------------------------------------------------------------------------------------------
        #                   ~~~~~~~~               ~~~~~~~~
        #                    |    |                 |    |
        # Computing h_I(W) = |    | (W - x([i]P)) = |    | (Zi * W - Xi) / Zi where x([i]P) = Xi/Zi
        #                    i in I                 i in I
        # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates

        hI = [[fp.fp_sub(0, iP[0]), iP[1]]
              for iP in I]  # we only need to negate x-coordinate of each point
        ptree_hI = poly_mul.product_tree(hI, sI)  # product tree of hI

        if not SCALED_REMAINDER_TREE:
            # Using scaled remainder trees
            ptree_hI = poly_redc.reciprocal_tree(
                {
                    'rpoly': [1],
                    'rdeg': 0,
                    'fpoly': [1],
                    'fdeg': 0,
                    'a': 1
                },
                2 * sJ + 1,
                ptree_hI,
                sI,
            )  # reciprocal of the root is used to compute sons' reciprocals

        else:
            # Using scaled remainder trees
            if sI < (2 * sJ - sI + 1):
                ptree_hI['reciprocal'], ptree_hI['a'] = poly_redc.reciprocal(
                    ptree_hI['poly'][::-1], sI + 1, 2 * sJ - sI + 1)
                ptree_hI['scaled'], ptree_hI['as'] = (
                    list(ptree_hI['reciprocal'][:sI]),
                    ptree_hI['a'],
                )

            else:
                ptree_hI['scaled'], ptree_hI['as'] = poly_redc.reciprocal(
                    ptree_hI['poly'][::-1], sI + 1, sI)
                ptree_hI['reciprocal'], ptree_hI['a'] = (
                    list(ptree_hI['scaled'][:(2 * sJ - sI + 1)]),
                    ptree_hI['as'],
                )

        # Polynomial D_j of algorithm 2 from https://eprint.iacr.org/2020/341 is not required,
        # but we need some some squares and products determined by list J
        # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates

        # Ensuring the cardinality of each ser coincide with the expected one
        assert len(I) == sI
        assert len(J) == sJ
        assert len(K) == sK

        return None

    # Next function perform algorithm 2 of https://eprint.iacr.org/2020/341 with input \alpha = 1 and \alpha = -1, and
    # then it computes the isogenous Montgomery curve coefficient
    def xISOG(A, i):

        nonlocal J
        nonlocal sJ
        nonlocal ptree_hI
        nonlocal sI
        nonlocal K
        nonlocal sK
        nonlocal XZJ4

        AA = fp.fp_add(A[0], A[0])  # 2A' + 4C
        AA = fp.fp_sub(AA, A[1])  # 2A'
        AA = fp.fp_add(AA, AA)  # 4A'

        # Polynomial D_j of algorithm 2 from https://eprint.iacr.org/2020/341 is not required,
        # but we need some some squares and products determined by list J
        # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates

        SUB_SQUARED = [0 for j in range(0, sJ, 1)]  #
        ADD_SQUARED = [0 for j in range(0, sJ, 1)]  #

        # List XZJ4 is required for degree-l isogeny evaluations...
        XZJ4 = [0 for j in range(0, sJ, 1)]  # 2*Xj*Zj
        for j in range(0, sJ, 1):

            SUB_SQUARED[j] = fp.fp_sub(J[j][0], J[j][1])  # (Xj - Zj)
            SUB_SQUARED[j] = fp.fp_sqr(SUB_SQUARED[j])  # (Xj - Zj)^2

            ADD_SQUARED[j] = fp.fp_add(J[j][0], J[j][1])  # (Xj + Zj)
            ADD_SQUARED[j] = fp.fp_sqr(ADD_SQUARED[j])  # (Xj + Zj)^2

            XZJ4[j] = fp.fp_sub(SUB_SQUARED[j], ADD_SQUARED[j])  # -4*Xj*Zj

        # --------------------------------------------------------------------------------------------------
        #                   ~~~~~~~~
        #                    |    |
        # Computing E_J(W) = |    | [ F0(W, x([j]P)) * alpha^2 + F1(W, x([j]P)) * alpha + F2(W, x([j]P)) ]
        #                    j in J
        # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates
        # In particular, for a degree-l isogeny construction, we need alpha = 1 and alpha = -1

        # EJ_0 is the one determined by alpha = 1
        EJ_0 = [[0, 0, 0] for j in range(0, sJ, 1)]
        # EJ_1 is the one determined by alpha = -1
        EJ_1 = [[0, 0, 0] for j in range(0, sJ, 1)]

        for j in range(0, sJ, 1):

            # However, each SUB_SQUARED[j] and ADD_SQUARED[j] should be multiplied by C
            tadd = fp.fp_mul(ADD_SQUARED[j], A[1])
            tsub = fp.fp_mul(SUB_SQUARED[j], A[1])

            # We require the double of tadd and tsub
            tadd2 = fp.fp_add(tadd, tadd)
            tsub2 = fp.fp_add(tsub, tsub)

            t1 = fp.fp_mul(XZJ4[j], AA)  #       A *(-4*Xj*Zj)

            # Case alpha = 1
            linear = fp.fp_sub(
                t1, tadd2)  #       A *(-4*Xj*Zj)  - C * (2 * (Xj + Zj)^2)
            EJ_0[j] = [tsub, linear, tsub]

            # Case alpha = -1
            linear = fp.fp_sub(
                tsub2, t1)  #       C * (2 * (Xj - Zj)^2) - A *(-4*Xj*Zj)
            EJ_1[j] = [tadd, linear, tadd]

        # The faster way for multiplying is using a divide-and-conquer approach
        poly_EJ_0 = poly_mul.product_selfreciprocal_tree(
            EJ_0,
            sJ)['poly']  # product tree of EJ_0 (we only require the root)
        poly_EJ_1 = poly_mul.product_selfreciprocal_tree(
            EJ_1,
            sJ)['poly']  # product tree of EJ_1 (we only require the root)

        if not SCALED_REMAINDER_TREE:
            # Remainder tree computation
            remainders_EJ_0 = poly_redc.multieval_unscaled(
                poly_EJ_0, 2 * sJ + 1, ptree_hI, sI)
            remainders_EJ_1 = poly_redc.multieval_unscaled(
                poly_EJ_1, 2 * sJ + 1, ptree_hI, sI)

        else:
            # Approach using scaled remainder trees
            if ptree_hI != None:
                poly_EJ_0 = poly_redc.poly_redc(poly_EJ_0, 2 * sJ + 1,
                                                ptree_hI)
                fg_0 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI,
                                                poly_EJ_0[::-1], sI)
                remainders_EJ_0 = poly_redc.multieval_scaled(
                    fg_0[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI)

                poly_EJ_1 = poly_redc.poly_redc(poly_EJ_1, 2 * sJ + 1,
                                                ptree_hI)
                fg_1 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI,
                                                poly_EJ_1[::-1], sI)
                remainders_EJ_1 = poly_redc.multieval_scaled(
                    fg_1[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI)
            else:
                remainders_EJ_0 = []
                remainders_EJ_1 = []

        # Multipying all the remainders
        r0 = poly_mul.product(remainders_EJ_0, sI)
        r1 = poly_mul.product(remainders_EJ_1, sI)

        # ---------------------------------------------------------------------------------
        # Now, we proceed by computing the missing part which is determined by K
        # Notice, the denominators are the same and then they annulled between them
        # In other words, it is not required to compute the product of all Zk's with k In K

        # Case alpha = 1
        hK_0 = [[fp.fp_sub(K[k][1], K[k][0])] for k in range(0, sK, 1)]
        hK_0 = poly_mul.product(hK_0,
                                sK)  # product of (Zk - Xk) for each k in K
        # Case alpha = -1
        hK_1 = [[fp.fp_add(K[k][1], K[k][0])] for k in range(0, sK, 1)]
        hK_1 = poly_mul.product(hK_1,
                                sK)  # product of (Zk + Xk) for each k in K

        # --------------------------------------------------------------
        # Now, we have all the ingredients for computing the image curve
        A24m = fp.fp_sub(A[0], A[1])  # A' - 2C

        A24 = fp.fp_exp(A[0], global_L[i])  # (A' + 2C)^l
        A24m = fp.fp_exp(A24m, global_L[i])  # (A' - 2C)^l

        t24m = fp.fp_mul(
            hK_1, r1
        )  # output of algorithm 2 with alpha =-1 and without the demoninator
        t24m = fp.fp_sqr(t24m)  # raised at 2
        t24m = fp.fp_sqr(t24m)  # raised at 4
        t24m = fp.fp_sqr(t24m)  # raised at 8

        t24 = fp.fp_mul(
            hK_0, r0
        )  # output of algorithm 2 with alpha = 1 and without the demoninator
        t24 = fp.fp_sqr(t24)  # raised at 2
        t24 = fp.fp_sqr(t24)  # raised at 4
        t24 = fp.fp_sqr(t24)  # raised at 8

        A24 = fp.fp_mul(A24, t24m)
        A24m = fp.fp_mul(A24m, t24)

        # Now, we have d = (A24m / A24) where the image Montgomery cuve coefficient is
        #      B'   2*(1 + d)   2*(A24 + A24m)
        # B = ---- = --------- = --------------
        #      C      (1 - d)     (A24 - A24m)
        # However, we required B' + 2C = 4*A24 and 4C = 4 * (A24 - A24m)

        t24m = fp.fp_sub(A24, A24m)  #   (A24 - A24m)
        t24m = fp.fp_add(t24m, t24m)  # 2*(A24 - A24m)
        t24m = fp.fp_add(t24m, t24m)  # 4*(A24 - A24m)

        t24 = fp.fp_add(A24, A24)  # 2 * A24
        t24 = fp.fp_add(t24, t24)  # 4 * A24

        # return [t24, t24m], ptree_hI, XZJ4
        return [t24, t24m]

    def xEVAL(P, A):

        AA = fp.fp_add(A[0], A[0])  # 2A' + 4C
        AA = fp.fp_sub(AA, A[1])  # 2A'
        AA = fp.fp_add(AA, AA)  # 4A'

        # --------------------------------------------------------------------------------------------------
        #                   ~~~~~~~~
        #                    |    |
        # Computing E_J(W) = |    | [ F0(W, x([j]P)) * alpha^2 + F1(W, x([j]P)) * alpha + F2(W, x([j]P)) ]
        #                    j in J
        # In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates
        # In particular, for a degree-l isogeny construction, we need alpha = 1 and alpha = -1

        # EJ_0 is the one determined by alpha = x
        EJ_0 = [[0, 0, 0] for j in range(0, sJ, 1)]
        # Notice, the corresponding EJ_1 that is determined by alpha = 1/x can be computed by using EJ_0

        XZ_add = fp.fp_add(P[0], P[1])  # X + Z
        XZ_sub = fp.fp_sub(P[0], P[1])  # X - Z

        AXZ2 = fp.fp_mul(P[0], P[1])  # X * Z
        t1 = fp.fp_sqr(P[0])  # X^2
        t2 = fp.fp_sqr(P[1])  # Z^2

        CX2Z2 = fp.fp_add(t1, t2)  #      X^2 + Z^2
        CX2Z2 = fp.fp_mul(CX2Z2, A[1])  # C * (X^2 + Z^2)

        AXZ2 = fp.fp_add(AXZ2, AXZ2)  #       2 * (X * Z)
        CXZ2 = fp.fp_mul(AXZ2, A[1])  # C  * [2 * (X * Z)]
        AXZ2 = fp.fp_mul(AXZ2, AA)  # A' * [2 * (X * Z)]

        for j in range(0, sJ, 1):

            XZj_add = fp.fp_add(J[j][0], J[j][1])  # Xj + Zj
            XZj_sub = fp.fp_sub(J[j][0], J[j][1])  # Xj - Zj

            t1 = fp.fp_mul(XZ_sub, XZj_add)  # (X - Z) * (Xj + Zj)
            t2 = fp.fp_mul(XZ_add, XZj_sub)  # (X + Z) * (Xj - Zj)

            # Computing the quadratic coefficient
            quadratic = fp.fp_sub(t1, t2)  #   2 * [(X*Zj) - (Z*Xj)]
            quadratic = fp.fp_sqr(quadratic)  # ( 2 * [(X*Zj) - (Z*Xj)] )^2
            quadratic = fp.fp_mul(A[1],
                                  quadratic)  # C * ( 2 * [(X*Zj) - (Z*Xj)] )^2

            # Computing the constant coefficient
            constant = fp.fp_add(t1, t2)  #   2 * [(X*Xj) - (Z*Zj)]
            constant = fp.fp_sqr(constant)  # ( 2 * [(X*Xj) - (Z*Zj)] )^2
            constant = fp.fp_mul(A[1],
                                 constant)  # C * ( 2 * [(X*Xj) - (Z*Zj)] )^2

            # Computing the linear coefficient
            # ----------------------------------------------------------------------------------------------------------
            # C * [ (-2*Xj*Zj)*(alpha^2 + 1) + (-2*alpha)*(Xj^2 + Zj^2)] + [A' * (-2*Xj*Zj) * (2*X*Z)] where alpha = X/Z
            t1 = fp.fp_add(J[j][0], J[j][1])  #     (Xj + Zj)
            t1 = fp.fp_sqr(t1)  #     (Xj + Zj)^2
            t1 = fp.fp_add(t1, t1)  # 2 * (Xj + Zj)^2
            t1 = fp.fp_add(
                t1,
                XZJ4[j])  # 2 * (Xj + Zj)^2 - (4*Xj*Zj) := 2 * (Xj^2 + Zj^2)
            t1 = fp.fp_mul(t1,
                           CXZ2)  # [2 * (Xj^2 + Zj^2)] * (2 * [ C * (X * Z)])

            t2 = fp.fp_mul(CX2Z2,
                           XZJ4[j])  # [C * (X^2 + Z^2)] * (-4 * Xj * Zj)
            t1 = fp.fp_sub(
                t2, t1
            )  # [C * (X^2 + Z^2)] * (-4 * Xj * Zj) - [2 * (Xj^2 + Zj^2)] * (2 * [ C * (X * Z)])

            t2 = fp.fp_mul(AXZ2,
                           XZJ4[j])  # (2 * [A' * (X * Z)]) * (-4 * Xj * Zj)
            linear = fp.fp_add(
                t1, t2)  # This is our desired equation but multiplied by 2
            linear = fp.fp_add(
                linear,
                linear)  # This is our desired equation but multiplied by 4
            # ----------------------------------------------------------------------------------------------------------

            # Case alpha = X / Z
            EJ_0[j] = [constant, linear, quadratic]

        # The faster way for multiplying is using a divide-and-conquer approach
        poly_EJ_0 = poly_mul.product_tree(
            EJ_0,
            sJ)['poly']  # product tree of EJ_0 (we only require the root)
        poly_EJ_1 = list(
            poly_EJ_0[::-1])  # product tree of EJ_1(x) = x^{2b + 1} EJ_0(1/X)

        if not SCALED_REMAINDER_TREE:
            # Remainder tree computation
            remainders_EJ_0 = poly_redc.multieval_unscaled(
                poly_EJ_0, 2 * sJ + 1, ptree_hI, sI)
            remainders_EJ_1 = poly_redc.multieval_unscaled(
                poly_EJ_1, 2 * sJ + 1, ptree_hI, sI)

        else:
            # Approach using scaled remainder trees
            if ptree_hI != None:
                poly_EJ_0 = poly_redc.poly_redc(poly_EJ_0, 2 * sJ + 1,
                                                ptree_hI)
                fg_0 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI,
                                                poly_EJ_0[::-1], sI)
                remainders_EJ_0 = poly_redc.multieval_scaled(
                    fg_0[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI)

                poly_EJ_1 = poly_redc.poly_redc(poly_EJ_1, 2 * sJ + 1,
                                                ptree_hI)
                fg_1 = poly_mul.poly_mul_middle(ptree_hI['scaled'], sI,
                                                poly_EJ_1[::-1], sI)
                remainders_EJ_1 = poly_redc.multieval_scaled(
                    fg_1[::-1], sI, [1] + [0] * (sI - 1), sI, ptree_hI, sI)
            else:
                remainders_EJ_0 = []
                remainders_EJ_1 = []

        # Multipying all the remainders
        r0 = poly_mul.product(remainders_EJ_0, sI)
        r1 = poly_mul.product(remainders_EJ_1, sI)

        # ---------------------------------------------------------------------------------
        # Now, we proceed by computing the missing part which is determined by K
        # Notice, the denominators are the same and then they annulled between them
        # In other words, it is not required to compute the product of all Zk's with k In K

        hK_0 = [[0]] * sK
        hK_1 = [[0]] * sK
        for k in range(0, sK, 1):

            XZk_add = fp.fp_add(K[k][0], K[k][1])  # Xk + Zk
            XZk_sub = fp.fp_sub(K[k][0], K[k][1])  # Xk - Zk
            t1 = fp.fp_mul(XZ_sub, XZk_add)  # (X - Z) * (Xk + Zk)
            t2 = fp.fp_mul(XZ_add, XZk_sub)  # (X + Z) * (Xk - Zk)

            # Case alpha = X/Z
            hK_0[k] = [fp.fp_sub(t1, t2)]  # 2 * [(X*Zk) - (Z*Xk)]

            # Case 1/alpha = Z/X
            hK_1[k] = [fp.fp_add(t1, t2)]  # 2 * [(X*Xk) - (Z*Zk)]

        hK_0 = poly_mul.product(hK_0,
                                sK)  # product of (XZk - ZXk) for each k in K
        hK_1 = poly_mul.product(hK_1,
                                sK)  # product of (XXk - ZZk) for each k in K

        # ---------------------------------------------------------------------------------
        # Now, unifying all the computations
        XX = fp.fp_mul(
            hK_1, r1
        )  # output of algorithm 2 with 1/alpha = Z/X and without the demoninator
        XX = fp.fp_sqr(XX)
        XX = fp.fp_mul(XX, P[0])

        ZZ = fp.fp_mul(
            hK_0, r0
        )  # output of algorithm 2 with alpha = X/Z and without the demoninator
        ZZ = fp.fp_sqr(ZZ)
        ZZ = fp.fp_mul(ZZ, P[1])

        return [XX, ZZ]

    # Get cost of the isogeny constructions and evaluations
    sI_list = None
    sJ_list = None

    def cISOG_and_cEVAL():

        nonlocal C_xISOG
        nonlocal C_xEVAL

        nonlocal sI_list
        nonlocal sJ_list

        if tuned:

            sI_list = []
            sJ_list = []
            try:
                f = open(resource_filename('sidh', 'data/ijk/' + prime))
            except Exception as ex:
                raise Exception(
                    "ijk data required for tuned mode not found: %r", ex)

            for i in range(0, n, 1):

                bc = f.readline()
                bc = [int(bci) for bci in bc.split()]
                sJ_list.append(bc[0])
                sI_list.append(bc[1])

            f.close()
        # First, we look for a full torsion point
        A = [2, 4]
        T_p, T_m = curve.full_torsion_points(A)

        for i in range(0, n, 1):

            if tuned:
                set_parameters_velu(sJ_list[i], sI_list[i], i)
            else:
                # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                # These paramters are required in KPs, xISOG, and xEVAL
                if global_L[i] == 3:
                    b = 0
                    c = 0
                else:
                    b = int(floor(sqrt(global_L[i] - 1) / 2.0))
                    c = int(floor((global_L[i] - 1.0) / (4.0 * b)))

                set_parameters_velu(b, c, i)

            # Getting an orderl-l point
            Tp = list(T_p)
            for j in range(i + 1, n, 1):
                Tp = curve.xMUL(Tp, A, j)

            # Cost of xISOG() and KPs()
            fp.set_zero_ops()
            KPs(Tp, A, i)
            t = fp.get_ops()
            C_xISOG[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            fp.set_zero_ops()
            B = xISOG(A, i)
            t = fp.get_ops()
            C_xISOG[i] += numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # xEVAL: kernel point determined by the next isogeny evaluation
            fp.set_zero_ops()
            T_p = xEVAL(T_p, A)

            # Cost of xEVAL
            fp.set_zero_ops()
            T_m = xEVAL(T_m, A)
            t = fp.get_ops()
            C_xEVAL[i] = numpy.array([t[0] * 1.0, t[1] * 1.0, t[2] * 1.0])

            # Updating the new next curve
            A = list(B)

        fp.set_zero_ops()
        assert curve.coeff(A) == 0x6
        return None

    # Now, we proceed to store all the correct costs
    cISOG_and_cEVAL()

    return attrdict(name='svelu', **locals())
Beispiel #23
0
def Poly_redc(poly_mul):
    fp = poly_mul.fp
    poly_mul_middle = poly_mul.poly_mul_middle
    poly_mul_modxn = poly_mul.poly_mul_modxn

    def reciprocal(f, flen, n):
        """
        This function computes the reciprocal of a given polynomial
        """
        if flen < n:
            # truncated reciprocal
            return reciprocal(f + [0] * (n - flen), n, n)

        if n == 0:
            # Super special base case
            return [1], 1

        if n == 1:

            # Base case when (1/f) mod x^n = 1
            return [1], f[0]

        elif n == 2:

            # Base case when (1/f) = f_0 - f_1*x
            return [f[0], fp.fp_sub(0, f[1])], fp.fp_sqr(f[0])

        elif n == 3:

            g, a = reciprocal(f[:2], 2, 2)

            t0 = fp.fp_sqr(f[1])
            t1 = fp.fp_mul(f[0], f[2])
            t2 = fp.fp_sub(t1, t0)
            t2 = fp.fp_mul(t2, f[0])
            return (
                [fp.fp_mul(g[0], a), fp.fp_mul(g[1], a), fp.fp_sub(0, t2)],
                fp.fp_sqr(a),
            )

        elif n == 4:

            # This case gives the same cost as the general case
            g, a = reciprocal(f[:2], 2, 2)
            t0 = fp.fp_sqr(f[1])
            t1 = fp.fp_mul(g[0], f[2])
            t2 = fp.fp_mul(g[0], f[3])
            t3 = fp.fp_mul(g[1], f[2])
            t0 = fp.fp_sub(t1, t0)
            t1 = fp.fp_add(t2, t3)
            t2 = fp.fp_mul(t0, g[0])
            t3 = fp.fp_mul(t0, g[1])
            t4 = fp.fp_mul(t1, g[0])
            t3 = fp.fp_add(t3, t4)
            return (
                [
                    fp.fp_mul(g[0], a),
                    fp.fp_mul(g[1], a),
                    fp.fp_sub(0, t2),
                    fp.fp_sub(0, t3),
                ],
                fp.fp_sqr(a),
            )

        else:

            m = n - (n // 2)  # Ceiling of n/2

            # Recursively call to reciprocal
            g, a = reciprocal(f, flen, m)

            """
            # Basic idea
            t = poly_mul_modxn(n, f[:n], n, g, m)                                   #  f * g          mod x^n
            t = [fp_sub(t[0], a) ] + t[1:n]                                         # (f * g - a)     mod x^n
            assert( [0]*m == t[:m] )                                                # the first m coefficients are equal zero
            t = poly_mul_modxn(n - m, g, m, t[m:], n - m)                           # (f * g - a) * g mod x^n

            # Finally, the reciprocal is a*g - (f*g - a)*g mod x^n
            t = [fp.fp_mul(a, g[i]) for i in range(0, m, 1) ] + [fp.fp_sub(0, t[i]) for i in range(0, n - m, 1) ]
            """

            # Basic idea but saving multiplication because of f*g - a is multiple of x^m
            t = poly_mul_middle(
                g, m, f[:n], n
            )  #  f * g          mod x^n (the last 'm >= n - m' coefficients)
            t = poly_mul_modxn(
                n - m, g, m, t[(2 * m - n) :], n - m
            )  # (f * g - a) * g mod x^n

            # Finally, the reciprocal is a*g - (f*g - a)*g mod x^n
            t = [fp.fp_mul(a, g[i]) for i in range(0, m, 1)] + [
                fp.fp_sub(0, t[i]) for i in range(0, n - m, 1)
            ]

            return t, fp.fp_sqr(a)

    def poly_redc(h, hlen, f):
        """
        Modular reduction in fp[x] with precomputation
        """

        flen = f['deg'] + 1

        if hlen < flen:
            # Base case, h(x) mod f(x) = h(x)
            return list(h) + [0] * (flen - hlen - 1)

        elif flen == 2 and hlen == 2:

            t0 = fp.fp_mul(h[0], f['poly'][1])  # h0 * f1
            t1 = fp.fp_mul(h[1], f['poly'][0])  # h1 * f0
            return [fp.fp_sub(t0, t1)]  # f1 * (h0 + h1*x) mod (f0 + f1*x)

        elif flen == 2 and hlen == 3:

            f0_squared = fp.fp_sqr(f['poly'][0])  # f0^2
            f1_squared = fp.fp_sqr(f['poly'][1])  # f1^2
            t = fp.fp_sub(f['poly'][0], f['poly'][1])  # f0 - f1

            t = fp.fp_sqr(t)  # (f0 - f1)^2
            t = fp.fp_sub(t, f0_squared)  # (f0 - f1)^2 - f0^2
            t = fp.fp_sub(
                t, f1_squared
            )  # (f0 - f1)^2 - f0^2 - f1^2 = -2*f0*f1

            f0_squared = fp.fp_add(f0_squared, f0_squared)  # 2*(f0^2)
            f1_squared = fp.fp_add(f1_squared, f1_squared)  # 2*(f1^2)

            t0 = fp.fp_mul(f0_squared, h[2])  # [2*(f0^2)] * h2
            t1 = fp.fp_mul(f1_squared, h[0])  # [2*(f1^2)] * h0
            t2 = fp.fp_mul(t, h[1])  # [-2*f0*f1] * h1
            return [
                fp.fp_add(t0, fp.fp_add(t1, t2))
            ]  # [2 * (f1^2)] * (h0 + h1*x + h2*x^2) mod (f0 + f1*x)

        elif flen == 3 and hlen == 3:

            f2h1 = fp.fp_mul(f['poly'][2], h[1])
            f2h0 = fp.fp_mul(f['poly'][2], h[0])
            f1h2 = fp.fp_mul(f['poly'][1], h[2])
            f0h2 = fp.fp_mul(f['poly'][0], h[2])
            return [fp.fp_sub(f2h0, f0h2), fp.fp_sub(f2h1, f1h2)]
            """
        elif flen == hlen:

            t0 = [fp.fp_mul(h[inner], f['poly'][fdeg - 1]) for inner in range(0, fdeg - 1, 1) ]
            t1 = [fp.fp_mul(h[fdeg - 1], f['poly'][inner]) for inner in range(0, fdeg - 1, 1) ]
            return [fp.fp_sub(t1[inner], t0[inner]) for inner in range(0, fdeg - 1, 1) ]
            """
        else:

            H = [h[i] for i in range(hlen - 1, -1, -1)]  # x^deg(h) * h(x^-1)
            Q = list(f['reciprocal'])  # (1/F) mod x^(deg(f) - deg(h))

            # (H/F) mod x^(deg(f) - deg(h))
            Q = poly_mul_modxn(
                hlen - flen + 1,
                Q[: (hlen - flen + 1)],
                hlen - flen + 1,
                H[: (hlen - flen + 1)],
                hlen - flen + 1,
            )

            q = [
                Q[i] for i in range(hlen - flen, -1, -1)
            ]  # x^deg(Q) * Q(x^-1) is the quotient
            qf = poly_mul_modxn(
                flen - 1, q, hlen - flen + 1, f['poly'], flen
            )  # (q * f) (x) has degree equals deg(h)

            # a*h(x) - (q * f)(x) will gives a polynomial of degree (deg(f) - 1)
            return [
                fp.fp_sub(fp.fp_mul(f['a'], h[i]), qf[i])
                for i in range(0, flen - 1, 1)
            ]

    def reciprocal_tree(r, glen, ptree_f, n):
        """
        Reciprocal tree of a given product tree
        """
        if n == 0:
            # Super special base case (nothing to do)
            return {
                'left': None,
                'right': None,
                'poly': [1],
                'deg': 0,
                'reciprocal': [1],
                'a': 1,
            }

        if n == 1:
            # We are in the leaf
            return {
                'left': None,
                'right': None,
                'poly': list(ptree_f['poly']),
                'deg': ptree_f['deg'],
                'reciprocal': [1],
                'a': 1,
            }

        if ptree_f['deg'] == 2 and (glen == 3):
            # Not required because of our redcution base cases
            return {
                'left': ptree_f['left'],
                'right': ptree_f['right'],
                'poly': list(ptree_f['poly']),
                'deg': ptree_f['deg'],
                'reciprocal': [1],
                'a': 1,
            }

        # At this point, we deal with a general case
        flen = ptree_f['deg'] + 1
        # R, A = reciprocal(ptree_f['poly'][::-1], flen, glen - flen + 1  )
        if r['rdeg'] <= (glen - flen):

            # This case requires a reciprocal computation
            R, A = reciprocal(ptree_f['poly'][::-1], flen, glen - flen + 1)

        else:

            # This case allows to use a polynomial multiplication modulo x^{glen - flen + 1}
            A = r['a']
            R = poly_mul_modxn(
                glen - flen + 1,
                r['rpoly'],
                r['rdeg'] + 1,
                r['fpoly'][::-1],
                r['fdeg'] + 1,
            )

        # Now, we proceed by recusion calls
        m = n - (n // 2)
        left = reciprocal_tree(
            {
                'rpoly': R,
                'rdeg': (glen - flen),
                'fpoly': ptree_f['right']['poly'],
                'fdeg': ptree_f['right']['deg'],
                'a': A,
            },
            flen - 1,
            ptree_f['left'],
            m,
        )
        right = reciprocal_tree(
            {
                'rpoly': R,
                'rdeg': (glen - flen),
                'fpoly': ptree_f['left']['poly'],
                'fdeg': ptree_f['left']['deg'],
                'a': A,
            },
            flen - 1,
            ptree_f['right'],
            n - m,
        )

        return {
            'left': left,
            'right': right,
            'poly': list(ptree_f['poly']),
            'deg': ptree_f['deg'],
            'reciprocal': R,
            'a': A,
        }

    def multieval_unscaled(g, glen, ptree_f, n):
        """
        Next function computes g(x) mod f_1(x), ..., g(x) mod f_n(x)
        """

        if n == 0:
            return [[1]]

        g_mod = poly_redc(g, glen, ptree_f)

        if n == 1:

            # Now, we have g corresponds with the initial G but now it is modulus a leaf of the product tree of f
            return [g_mod]

        else:

            m = n - (n // 2)
            # Reducing g(x) modulo the current node polynomial
            left = multieval_unscaled(
                g_mod, ptree_f['deg'], ptree_f['left'], m
            )
            right = multieval_unscaled(
                g_mod, ptree_f['deg'], ptree_f['right'], n - m
            )
            return left + right

    def multieval_scaled(g, glen, f, flen, ptree_f, n):
        """
        Next functions computes the scaled remainder tree
        """

        if n == 0:
            return [[1]]

        # fg = poly_mul_middle(f, flen, g, glen)
        if flen == n and glen == n and n > 1:
            fg = list(g)
        else:
            fg = poly_mul_middle(f, flen, g, glen)

        if n == 1:
            # The last coefficient should be the desire modular reduction with linear modulus
            if fg != []:
                return [[fg[-1]]]
            else:
                return [[1]]

        m = n - (n // 2)
        left = multieval_scaled(
            fg,
            flen,
            ptree_f['right']['poly'],
            ptree_f['right']['deg'] + 1,
            ptree_f['left'],
            m,
        )
        right = multieval_scaled(
            fg,
            flen,
            ptree_f['left']['poly'],
            ptree_f['left']['deg'] + 1,
            ptree_f['right'],
            n - m,
        )
        return left + right

    return attrdict(locals())
Beispiel #24
0
def csidh_test(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    full_torsion_points = algo.curve.full_torsion_points
    coeff = algo.curve.coeff
    p = algo.params.p
    global_L = L = algo.params.L
    n = algo.params.n
    A = algo.curve.A
    xMUL = algo.curve.xMUL
    if algo.formula.name != 'tvelu':
        set_parameters_velu = algo.formula.set_parameters_velu
        print_parameters_velu = algo.formula.print_parameters_velu
        sI = algo.formula.sI
        HYBRID_BOUND = algo.formula.HYBRID_BOUND
    get_ops = algo.fp.get_ops
    set_zero_ops = algo.fp.set_zero_ops
    KPs = algo.formula.KPs
    show_ops = algo.fp.show_ops
    xISOG = algo.formula.xISOG
    xEVAL = algo.formula.xEVAL

    total_cost = [0, 0, 0]
    print("p := 0x%X;" % p)
    print("fp := GF(p);")
    print("P<x> := PolynomialRing(fp);")

    print("E := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))

    if True:

        # T_p belongs to E[pi - 1]
        # T_m belongs to E[pi + 1]
        T_p, T_m = full_torsion_points(A)

    else:

        # T_m belongs to E[pi - 1]
        # T_p belongs to E[pi + 1]
        T_m, T_p = full_torsion_points(A)

    assert len(L) == n
    print(
        "// Now, we proceed by performing xISOG with input curve equals the output curve of the previous one experiment."
    )
    for idx in range(0, n, 1):

        # -------------------------------------------------------------
        # Random kernel point
        Tp = list(T_p)
        for i in range(idx + 1, n, 1):
            Tp = xMUL(Tp, A, i)

        print("// l:\t%7d |" % global_L[idx], end="")
        total_cost = [0, 0, 0]

        if setting.formula != 'tvelu':

            if setting.tuned:
                set_parameters_velu(sJ_list[idx], sI_list[idx], idx)

            else:
                # -------------------------------------------------------------
                # Parameters sJ and sI correspond with the parameters b and b' from example 4.12 of https://eprint.iacr.org/2020/341
                # These paramters are required in KPs, xISOG, and xEVAL
                if global_L[idx] == 3:
                    b = 0
                    c = 0
                else:
                    b = int(floor(sqrt(global_L[idx] - 1) / 2.0))
                    c = int(floor((global_L[idx] - 1.0) / (4.0 * b)))

                set_parameters_velu(b, c, idx)

            print_parameters_velu()

        # -------------------------------------------------------------
        # KPs procedure
        set_zero_ops()
        KPs(Tp, A, idx)
        show_ops("Kps", 1.0, 0.0, False)
        t = get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xISOG
        set_zero_ops()
        B = xISOG(A, idx)
        show_ops("xISOG", 1.0, 0.0, False)
        t = get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]

        # -------------------------------------------------------------
        # xEVAL: kernel point determined by the next isogeny evaluation
        set_zero_ops()
        if setting.formula == 'tvelu' or (setting.formula == 'hvelu'
                                          and global_L[idx] <= HYBRID_BOUND):
            T_p = xEVAL(T_p, idx)
        else:
            T_p = xEVAL(T_p, A)

        # xEVAL bench
        set_zero_ops()
        if setting.formula == 'tvelu' or (setting.formula == 'hvelu'
                                          and global_L[idx] <= HYBRID_BOUND):
            T_m = xEVAL(T_m, idx)
        else:
            T_m = xEVAL(T_m, A)

        show_ops("xEVAL", 1.0, 0.0, False)
        t = get_ops()
        total_cost[0] += t[0]
        total_cost[1] += t[1]
        total_cost[2] += t[2]
        print("|| cost: %7d" % (total_cost[0] + total_cost[1]), end=" ")
        print("|| ratio: %1.3f" % ((total_cost[0] + total_cost[1]) /
                                   (global_L[idx] + 2.0)))

        # assert(validate(B))
        A = list(B)

        # print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))
        # print("assert(Random(B) * (p + 1) eq B!0);")
        # print("BOOL, Q := IsPoint(B, fp!%d/%d);" % (T_m[0], T_m[1]))
        # print("assert(BOOL);")

    print(
        "\n// All the l_i's have been processed, output of xISOG corresponds with the given below"
    )
    print("B := EllipticCurve(x^3 + 0x%X * x^2 + x);" % coeff(A))
    print("assert(Random(B) * (p + 1) eq B!0);")

    print(
        "\n\"If no errors were showed using magma calculator, then all experiments were successful passed!\";"
    )
    print("// copy and paste it at http://magma.maths.usyd.edu.au/calc/\n")
    return attrdict(name='csidh-test', **locals())
Beispiel #25
0
def csidh_bounds(ctx):
    algo = ctx.meta['sidh.kwargs']['algo']
    setting = ctx.meta['sidh.kwargs']
    L = algo.params.L
    n = algo.params.n
    security = algo.gae.security
    strategy_block_cost = algo.gae.strategy_block_cost
    basis = numpy.eye(n, dtype=int)
    measure = algo.curve.measure

    print_intv = lambda v, n: ', '.join(list(map(format, v, ['2d'] * n)))

    # MITM procedure
    keyspace = 256.00  # For ensuring 128-bits of classical security
    # keyspace = 384.00              # For ensuring 192-bits of classical security
    # vOW Golden Collision Search
    # keyspace = 220.2295746338436   # For ensuring 128-bits of classical security
    # keyspace = 305.5629079671769   # For ensuring 192-bits of classical security

    # The next function computes the set \mu() given in the paper
    def neighboring_intvec(seq_i, L, IN, OUT):
        nonlocal keyspace
        if OUT[2] >= keyspace:
            return OUT

        else:
            minimum = IN
            if measure(IN[1]) >= measure(OUT[1]):

                for j in seq_i:

                    current_cost, _, _, _, _ = strategy_block_cost(
                        L, OUT[0] + basis[j]
                    )
                    tmp = neighboring_intvec(
                        seq_i,
                        L,
                        IN,
                        (
                            OUT[0] + basis[j],
                            current_cost,
                            security(OUT[0] + basis[j], len(L)),
                        ),
                    )
                    if measure(minimum[1]) >= measure(tmp[1]):
                        minimum = tmp

            return minimum

    # Finally, the next functions is the implementation of algorithm 2.0
    def optimal_bounds(L, b, r):

        assert r >= 1
        n = len(L)

        RNC, _, _, _, _ = strategy_block_cost(L, b)
        SEC = security(b, n)
        e = b

        for i in range(0, n, 1):

            # The algorithm proceed by looking the best bounds when e_i <- e_i - 1
            seq_i = [k for k in range(n) if k != i]
            (e_tmp, RNC_tmp, SEC_tmp) = (e, RNC, SEC)
            if e[i] > r:

                # Set the new possible optimal bounds
                temporal_cost, _, _, _, _ = strategy_block_cost(
                    L, e - r * basis[i]
                )
                (e_tmp, RNC_tmp, SEC_tmp) = neighboring_intvec(
                    seq_i,
                    L,
                    (e, RNC, SEC),
                    (
                        e - r * basis[i],
                        temporal_cost,
                        security(e - r * basis[i], n),
                    ),
                )

            (e, RNC, SEC) = min(
                [(e_tmp, RNC_tmp, SEC_tmp), (e, RNC, SEC)],
                key=lambda t: measure(t[1]),
            )

            print("[Security := %f]" % SEC, end="\t")
            print(
                "decreasing: e_{"
                + print_intv([i], 1)
                + "}"
                + ", and increasing each e_j with j != "
                + print_intv([i], 1)
                + "; current optimal running-time: %7.3f" % measure(RNC)
            )
            print("[" + print_intv(e, n) + "]\n")

        # --------------------------------------------------------------------------------------------------
        f = open(
            tmp_dir
            + "csidh_"
            + setting.prime
            + "_"
            + setting.style
            + "_m"
            + str(setting.benchmark)
            + ".py",
            "w",
        )
        f.write('m = [' + ', '.join([str(ei) for ei in e[::-1]]) + ']')
        f.close()
        # --------------------------------------------------------------------------------------------------
        return (e, RNC)

    ''' -------------------------------------------------------------------------------------
        Number of degree-(l_i) isogeny constructions to be performed: m_i
        ------------------------------------------------------------------------------------- '''

    # ==========================================================================
    m = setting.benchmark

    # ---
    k = 3
    # Next integer vector bount is given in Onuki et al. manuscript
    print(
        "\n_______________________________________________________________________________________________________________________________"
    )
    print("List of small odd primes")
    printl("L", L[::-1], n // k)
    print("\nInitial integer vector of bounts (b_0, ..., b_%d)" % n)

    if setting.benchmark == 1:
        if setting.style == 'wd1' or setting.style == 'df':
            assert n >= 221
            e = [1] * 221 + [0] * (n - 221)
        else:
            assert n >= 139
            e = [1] * 139 + [0] * (n - 139)

        # --------------------------------------------------------------------------------------------------
        f = open(
            tmp_dir
            + "csidh_"
            + setting.prime
            + "_"
            + setting.style
            + "_m"
            + str(setting.benchmark)
            + ".py",
            "w",
        )
        f.write('m = [' + ', '.join([str(ei) for ei in e]) + ']')
        f.close()
        # --------------------------------------------------------------------------------------------------

    else:
        e = [m] * (n - 1) + [(3 * m) // 2]
        e = numpy.array(e)
        stop = False
        for i in range(0, n, 1):
            for j in range(0, m, 1):
                e = e - basis[i]
                if security(e, n) < keyspace:
                    e = e + basis[i]
                    stop = True
                    break

            if stop:
                break
        printl("e", e, n // k)
        RUNNING_TIME, _, _, _, _ = strategy_block_cost(L[::-1], e)

        print(
            "// Number of field operations (GAE):\t%1.6f x M + %1.6f x S + %1.6f x a := %1.6f x M"
            % (
                RUNNING_TIME[0] / (10.0 ** 6),
                RUNNING_TIME[1] / (10.0 ** 6),
                RUNNING_TIME[2] / (10.0 ** 6),
                measure(RUNNING_TIME) / (10.0 ** 6),
            )
        )
        print("\tSecurity ~ %f\n" % security(e, n))

        print(
            "_______________________________________________________________________________________________________________________________"
        )
        print("We proceed by searching a better integer vector of bounds\n")
        r = 1
        for k in range(1, int(ceil((1.0 * m) / (1.0 * r)))):
            e, RNC = optimal_bounds(L[::-1], e, r)

        print(
            "_______________________________________________________________________________________________________________________________\n"
        )
    return attrdict(name='bounds', **locals())