From dc836861fd9aa9dba0a023a11dc97d69582c6afd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20M=C3=B6ller?= <nisse@lysator.liu.se>
Date: Fri, 6 Feb 2015 20:22:56 +0100
Subject: [PATCH] Added reference implementation

---
 .gitignore          |   1 +
 Makefile            |   3 +
 src/.gitignore      |   2 +
 src/Makefile        |   5 ++
 src/ed25519-test.py |  57 ++++++++++++++++
 src/ed25519.py      | 159 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 227 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 src/.gitignore
 create mode 100644 src/Makefile
 create mode 100644 src/ed25519-test.py
 create mode 100644 src/ed25519.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/Makefile b/Makefile
index d8bd028..a4a0c47 100644
--- a/Makefile
+++ b/Makefile
@@ -10,3 +10,6 @@ $(D).txt: $(D).xml
 
 $(D).html: $(D).xml
 	xml2rfc $< $@
+
+check:
+	cd src && $(MAKE) $@
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..2a6ead3
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+/__pycache__
+/sign.input
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..74a3136
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,5 @@
+all:
+check:
+	wget -N http://ed25519.cr.yp.to/python/sign.input
+	python3 ed25519-test.py < sign.input
+.PHONY: all check
diff --git a/src/ed25519-test.py b/src/ed25519-test.py
new file mode 100644
index 0000000..78a2952
--- /dev/null
+++ b/src/ed25519-test.py
@@ -0,0 +1,57 @@
+import sys
+import binascii
+
+from ed25519 import *
+
+def point_valid(P):
+    zinv = modp_inv(P[2])
+    x = P[0] * zinv % p
+    y = P[1] * zinv % p
+    assert (x*y - P[3]*zinv) % p == 0
+    return (-x*x + y*y - 1 - d*x*x*y*y) % p == 0
+
+assert point_valid(G)
+Z = (0, 1, 1, 0)
+assert point_valid(Z)
+
+assert point_equal(Z, point_add(Z, Z))
+assert point_equal(G, point_add(Z, G))
+assert point_equal(Z, point_mul(0, G))
+assert point_equal(G, point_mul(1, G))
+assert point_equal(point_add(G, G), point_mul(2, G))
+for i in range(0, 100):
+    assert point_valid(point_mul(i, G))
+assert point_equal(Z, point_mul(q, G))
+
+def munge_string(s, pos, change):
+    return (s[:pos] +
+            int.to_bytes(s[pos] ^ change, 1, "little") +
+            s[pos+1:])
+
+# Read a file in the format of
+# http://ed25519.cr.yp.to/python/sign.input
+lineno = 0
+while True:
+    line = sys.stdin.readline()
+    if not line:
+        break
+    lineno = lineno + 1
+    print(lineno)
+    fields = line.split(":")
+    secret = (binascii.unhexlify(fields[0]))[:32]
+    public = binascii.unhexlify(fields[1])
+    msg = binascii.unhexlify(fields[2])
+    signature = binascii.unhexlify(fields[3])[:64]
+
+    assert public == secret_to_public(secret)
+    assert signature == sign(secret, msg)
+    assert verify(public, msg, signature)
+    if len(msg) == 0:
+        bad_msg = b"x"
+    else:
+        bad_msg = munge_string(msg, len(msg) // 3, 4)
+    assert not verify(public, bad_msg, signature)
+    bad_signature = munge_string(signature, 20, 8)
+    assert not verify(public, msg, bad_signature)
+    bad_signature = munge_string(signature, 40, 16)
+    assert not verify(public, msg, bad_signature)
diff --git a/src/ed25519.py b/src/ed25519.py
new file mode 100644
index 0000000..2a1553e
--- /dev/null
+++ b/src/ed25519.py
@@ -0,0 +1,159 @@
+# Loosely based on the public domain code at
+# http://ed25519.cr.yp.to/software.html
+#
+# Needs python-3.2
+
+import hashlib
+
+
+def sha512(s):
+    return hashlib.sha512(s).digest()
+
+# Base field Z_p
+p = 2**255 - 19
+
+
+def modp_inv(x):
+    return pow(x, p-2, p)
+
+# Curve constant
+d = -121665 * modp_inv(121666) % p
+
+# Group order
+q = 2**252 + 27742317777372353535851937790883648493
+
+
+def sha512_modq(s):
+    return int.from_bytes(sha512(s), "little") % q
+
+# Points are represented as tuples (X, Y, Z, T) of extended coordinates,
+# with x = X/Z, y = Y/Z, x*y = T/Z
+
+
+def point_add(P, Q):
+    A = (P[1]-P[0])*(Q[1]-Q[0]) % p
+    B = (P[1]+P[0])*(Q[1]+Q[0]) % p
+    C = 2 * P[3] * Q[3] * d % p
+    D = 2 * P[2] * Q[2] % p
+    E = B-A
+    F = D-C
+    G = D+C
+    H = B+A
+    return (E*F, G*H, F*G, E*H)
+
+
+# Computes Q = s * Q
+def point_mul(s, P):
+    Q = (0, 1, 1, 0)  # Neutral element
+    while s > 0:
+        # Is there any bit-set predicate?
+        if s & 1:
+            Q = point_add(Q, P)
+        P = point_add(P, P)
+        s >>= 1
+    return Q
+
+
+def point_equal(P, Q):
+    # x1 / z1 == x2 / z2  <==>  x1 * z2 == x2 * z1
+    if (P[0] * Q[2] - Q[0] * P[2]) % p != 0:
+        return False
+    if (P[1] * Q[2] - Q[1] * P[2]) % p != 0:
+        return False
+    return True
+
+# Square root of -1
+modp_sqrt_m1 = pow(2, (p-1) // 4, p)
+
+
+# Compute corresponding x coordinate, with low bit corresponding to sign,
+# or return None on failure
+def recover_x(y, sign):
+    x2 = (y*y-1) * modp_inv(d*y*y+1)
+    if x2 == 0:
+        if sign:
+            return None
+        else:
+            return 0
+
+    # Compute square root of x2
+    x = pow(x2, (p+3) // 8, p)
+    if (x*x - x2) % p != 0:
+        x = x * modp_sqrt_m1 % p
+    if (x*x - x2) % p != 0:
+        return None
+
+    if (x & 1) != sign:
+        x = p - x
+    return x
+
+# Base point
+g_y = 4 * modp_inv(5) % p
+g_x = recover_x(g_y, 0)
+G = (g_x, g_y, 1, g_x * g_y % p)
+
+
+def point_compress(P):
+    zinv = modp_inv(P[2])
+    x = P[0] * zinv % p
+    y = P[1] * zinv % p
+    return int.to_bytes(y | ((x & 1) << 255), 32, "little")
+
+
+def point_decompress(s):
+    if len(s) != 32:
+        raise Exception("Invalid input length for decompression")
+    y = int.from_bytes(s, "little")
+    sign = y >> 255
+    y &= (1 << 255) - 1
+
+    x = recover_x(y, sign)
+    if x is None:
+        return None
+    else:
+        return (x, y, 1, x*y % p)
+
+
+def secret_expand(secret):
+    if len(secret) != 32:
+        raise Exception("Bad size of private key")
+    h = sha512(secret)
+    a = int.from_bytes(h[:32], "little")
+    a &= (1 << 254) - 8
+    a |= (1 << 254)
+    return (a, h[32:])
+
+
+def secret_to_public(secret):
+    (a, dummy) = secret_expand(secret)
+    return point_compress(point_mul(a, G))
+
+
+def sign(secret, msg):
+    a, prefix = secret_expand(secret)
+    A = point_compress(point_mul(a, G))
+    r = sha512_modq(prefix + msg)
+    R = point_mul(r, G)
+    Rs = point_compress(R)
+    h = sha512_modq(Rs + A + msg)
+    s = (r + h * a) % q
+    return Rs + int.to_bytes(s, 32, "little")
+
+
+def verify(public, msg, signature):
+    if len(public) != 32:
+        raise Exception("Bad public-key length")
+    if len(signature) != 64:
+        Exception("Bad signature length")
+    A = point_decompress(public)
+    if not A:
+        return False
+    Rs = signature[:32]
+    R = point_decompress(Rs)
+    if not R:
+        return False
+    s = int.from_bytes(signature[32:], "little")
+    h = sha512_modq(Rs + public + msg)
+    sB = point_mul(s, G)
+    hA = point_mul(h, A)
+    return point_equal(sB, point_add(R, hA))
-- 
GitLab