# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: SageMath 9.2
#     language: sage
#     name: sagemath
# ---

from string import ascii_lowercase

# +
import random

def generate_keys(q):
    
    K = GF(q, 'a')
    invertible_element = None
    
        
    while not invertible_element:
        e = Integer(K.random_element())
        if gcd(e, q-1) == 1:
            #print(gcd(e, q-1))
            invertible_element = e
    
    d = power_mod(Integer(invertible_element), -1, q-1)
    
    return Integer(invertible_element), Integer(d)

# -

q = 100019
eA, dA = generate_keys(q)
eB, dB = generate_keys(q)


# +
def MO_step1(m, q, eA, chunk_size=None):
    
    digits = int(log(q,10)) + 1
    if chunk_size == None:
        chunk_size = int(log(q,10))
        
    if chunk_size % 2 == 1:
        chunk_size -= 1
    
    m_encoded = "".join(["{:02d}".format(ord(x)-97) for x in m.lower() if x in ascii_lowercase]) 
    
    m_encoded = m_encoded + ((chunk_size - (len(m_encoded)% chunk_size )) % chunk_size)/2 * '29'
    
    f = len(m_encoded)/chunk_size
    chunks = [ int(m_encoded[i*chunk_size:(i+1)*chunk_size]) for i in range(f) ]
    
    # https://github.com/sagemath/sage/blob/222059565bc2166f29c50a6d85db7992589098c2/src/sage/arith/misc.py#L2201
    ciphered = [power_mod(x, eA, q) for x in chunks]
    
    formated = "{:0" + str(digits) + "d}"
    
    return "".join([formated.format(cn) for cn in ciphered])

def MO_step2(m, q, eB, chunk_size=None):
    
    digits_n = int(math.log(q, 10)) + 1
    
    if chunk_size == None:
        chunk_size = digits_n - 1
        
    if chunk_size % 2 == 1:
        chunk_size -= 1
    
    ff = len(m)/digits_n
    dk = [power_mod(int(m[i*digits_n:(i+1)*digits_n]), eB, q) for i in range(ff)]
    
    formated = "{:0" + str(digits_n) + "d}"
    chars = "".join([formated.format(cn) for cn in dk])
    return chars

def MO_step3(m, q, dA, chunk_size=None):
    return MO_step2(m, q, dA, chunk_size)

def MO_step4(m, q, dB, chunk_size=None):
    final =  MO_step2(m, q, dB, chunk_size)
    #print("final: ", final)
    digits_n = int(math.log(q, 10)) + 1
    
    if chunk_size == None:
        chunk_size = digits_n - 1
        
    if chunk_size % 2 == 1:
        chunk_size -= 1
    
    ff = len(m)/digits_n
    dk = [int(final[i*digits_n:(i+1)*digits_n]) for i in range(ff)]
    
    formated = "{:0" + str(chunk_size) + "d}"
    chars = "".join([formated.format(cn) for cn in dk])
    return "".join([chr(int(chars[i*2:(i+1)*2])+97) for i in range(len(chars)/2)])


# -

s1 = MO_step1("Juanma abreme la frontera", q, eA); print(s1)
s2 = MO_step2(s1, q, eB); print(s2)
s3 = MO_step3(s2, q, dA); print(s3)
s4 = MO_step4(s3, q, dB); print(s4)

StarcoderdataJupyterScriptsDedupFiltered