1

https://www.rfc-editor.org/rfc/rfc7748 gives a simple implementation of scalar multiplication on curve25519. They have some Python code and some pseudocode, and I tried my best to connect all of it into a single Python application. There is also a piece of code which I had to write from scratch, and by that I mean exponentiation of big numbers modulo p. The final product doesn't seem to work. It fails on elementary stuff like multiplying the base element by 1 (that is X25519(1, 9) != 9), and on tests provided by the document itself. At first I thought that I may be passing incorrectly formatted input, but I tried many combinations of the test values and it seems like the issue isn't there. My current guess is that I got the modulo arithmetic wrong. Please help Here's my code:

p = 2**255 - 19
a24 = 121665
bits = 255

def power_mod_p(b, e):
    powers = [b]
    for i in range(1, 256):
        powers.append((powers[i-1] ** 2) % p)

    result = 1
    for i in range(256):
        if (e >> i) & 1:
            result = (result * powers[i]) % p
    return result

def cswap(swap, x_2, x_3):
    dummy = (0 - swap) & (x_2 ^ x_3)
    x_2 = x_2 ^ dummy
    x_3 = x_3 ^ dummy
    return (x_2, x_3)

def X25519(k, u):
    x_1 = u
    x_2 = 1
    z_2 = 0
    x_3 = u
    z_3 = 1
    swap = 0

    for t in reversed(range(255)):
        k_t = (k >> t) & 1
        swap = swap ** k_t
        (x_2, x_3) = cswap(swap, x_2, x_3)
        (z_2, z_3) = cswap(swap, z_2, z_3)
        swap = k_t

        A = x_2 + z_2
        AA = A*A
        B = x_2 - z_2
        BB = B*B
        E = AA - BB
        C = x_3 + z_3
        D = x_3 - z_3
        DA = D * A
        CB = C * B
        x_3 = ((DA + CB) ** 2) % p
        z_3 = (x_1 * (DA - CB) ** 2) % p
        x_2 = (AA * BB) % p
        z_2 = (E * (AA + a24 * E)) % p

    (x_2, x_3) = cswap(swap, x_2, x_3)
    (z_2, z_3) = cswap(swap, z_2, z_3)
    return (x_2 * (power_mod_p(z_2, p - 2))) % p

def main():
# this first test is from the document, and it produces a bad result
    print(hex(X25519(31029842492115040904895560451863089656472772604678260265531221036453811406496, 34426434033919594451155107781188821651316167215306631574996226621102155684838)))

    print(X25519(1, 9)) # should print 9 according to the theory, instead prints nonsense

if __name__ == "__main__":
    main()

If you take a look at the end of the X25519 function, you will see that I added % p in some places. I assume that the pseudocode didn't explicitly include something like this, because it is mensioned in the document that all calculations are performed modulo p.

Another idea I just had is that maybe I interpreted some of the pseudocode incorrectly. For example, I assumed that the pseudocode's a ^= b is equivalent to Python's a = a ** b. I'm not 100% sure about it, because I see the '^' symbol used for XOR most of the times, but at some point in the document they explicitly do something like x XOR y to denote XOR, and I don't think that XORing with 2 makes the most sense. But I don't know anything at this point. I've spent much more time than I would like to admit trying to figure out what's wrong here. I will very fondly remember anyone who would be able to help me out.

Miodek
  • 55
  • 5
  • 2
    The bug is the line `swap = swap ** k_t` in function `X25519(k,u)`. This is to be replaced with `swap ^= k_t`. Also, it is more efficient to replace `(power_mod_p(z_2, p - 2))` with `pow(z_2, p - 2, p)` (this makes the custom implementation redundant). With these changes it works, see online here: https://www.jdoodle.com/ia/LyD. You can find an RFC near implementation here: https://asecuritysite.com/curve25519/python_25519ecdh – Topaco Aug 31 '23 at 20:03
  • Thank you very much @Topaco! Funny how much of a headache a ^ is capable of causing. Also I didn't know about `pow(z_2, p - 2, p)`. Cool stuff – Miodek Aug 31 '23 at 20:48

0 Answers0