Commit dc836861 authored by Niels Möller's avatar Niels Möller

Added reference implementation

parent 77bb2d6f
......@@ -10,3 +10,6 @@ $(D).txt: $(D).xml
$(D).html: $(D).xml
xml2rfc $< $@
check:
cd src && $(MAKE) $@
/__pycache__
/sign.input
all:
check:
wget -N http://ed25519.cr.yp.to/python/sign.input
python3 ed25519-test.py < sign.input
.PHONY: all check
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)
# 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))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment