Replace Ed25519 implementation with much faster pyca/ed25519

This commit is contained in:
Michał Sałaban 2019-11-20 01:09:56 +01:00
parent c5c53adb10
commit 9aee6564e7
3 changed files with 193 additions and 104 deletions

View file

@ -37,12 +37,15 @@ Released under the BSD 3-Clause License. See `LICENSE.txt`_.
Copyright (c) 2017-2018 Michał Sałaban <michal@salaban.info> and Contributors: `lalanza808`_, `cryptochangements34`_, `atward`_, `rooterkyberian`_, `brucexiu`_,
`lialsoftlab`_, `moneroexamples`_.
Copyright (c) 2016 The MoneroPy Developers (``monero/base58.py`` and ``monero/ed25519.py`` taken from `MoneroPy`_)
Copyright (c) 2016 The MoneroPy Developers (``monero/base58.py`` taken from `MoneroPy`_)
Copyright (c) 2011-2013 `pyca/ed25519`_ Developers (``monero/ed25519.py``)
Copyright (c) 2011 thomasv@gitorious (``monero/seed.py`` based on `Electrum`_)
.. _`LICENSE.txt`: LICENSE.txt
.. _`MoneroPy`: https://github.com/bigreddmachine/MoneroPy
.. _`pyca/ed25519`: https://github.com/pyca/ed25519
.. _`Electrum`: https://github.com/spesmilo/electrum
.. _`lalanza808`: https://github.com/lalanza808

View file

@ -1,16 +1,46 @@
# The reference Ed25519 software is in the public domain.
# Source: https://ed25519.cr.yp.to/python/ed25519.py
# ed25519.py - Optimized version of the reference implementation of Ed25519
#
# Parts Copyright (c) 2016 The MoneroPy Developers. Released under the BSD 3-Clause
# Parts taken from https://github.com/monero-project/mininero/blob/master/ed25519ietf.py
# Written in 2011? by Daniel J. Bernstein <djb@cr.yp.to>
# 2013 by Donald Stufft <donald@stufft.io>
# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
# 2013 by Greg Price <price@mit.edu>
# 2019 by Michal Salaban <michal@salaban.info>
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
from binascii import hexlify, unhexlify
import hashlib
import operator as _oper
import sys as _sys
"""
NB: This code is not safe for use with secret keys or secret data.
The only safe use of this code is for verifying signatures on public messages.
# Set up byte handling for Python 2 or 3
if _sys.version_info.major == 2: # pragma: no cover
Functions for computing the public key of a secret key and for signing
a message are included, namely publickey_unsafe and signature_unsafe,
for testing purposes only.
The root of the problem is that Python's long-integer arithmetic is
not designed for use in cryptography. Specifically, it may take more
or less time to execute an operation depending on the values of the
inputs, and its memory access patterns may also depend on the inputs.
This opens it to timing and cache side-channel attacks which can
disclose data to an attacker. We rely on Python's long-integer
arithmetic, so we cannot handle secrets without risking their disclosure.
"""
import binascii
import operator
import sys
if sys.version_info >= (3,): # pragma: no cover
indexbytes = operator.getitem
intlist2bytes = bytes
int2byte = operator.methodcaller("to_bytes", 1, "big")
else: # pragma: no cover
int2byte = chr
range = xrange
@ -19,32 +49,51 @@ if _sys.version_info.major == 2: # pragma: no cover
def intlist2bytes(l):
return b"".join(chr(c) for c in l)
else: # pragma: no cover
indexbytes = _oper.getitem
intlist2bytes = bytes
int2byte = _oper.methodcaller("to_bytes", 1, "big")
b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
def expmod(b, e, m):
if e == 0: return 1
t = expmod(b, e//2, m)**2 % m
if e & 1: t = (t*b) % m
return t
def inv(x):
return expmod(x, q-2, q)
def pow2(x, p):
"""== pow(x, 2**p, q)"""
while p > 0:
x = x * x % q
p -= 1
return x
def inv(z):
# Adapted from curve25519_athlon.c in djb's Curve25519.
z2 = z * z % q # 2
z9 = pow2(z2, 2) * z % q # 9
z11 = z9 * z2 % q # 11
z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
d = -121665 * inv(121666) % q
I = pow(2, (q - 1) // 4, q)
d = -121665 * inv(121666)
I = expmod(2, (q-1)//4, q)
def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx, (q+3)//8, q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
xx = (y * y - 1) * inv(d * y * y + 1)
x = pow(xx, (q + 3) // 8, q)
if (x * x - xx) % q != 0:
x = (x * I) % q
if x % 2 != 0:
x = q-x
return x
def compress(P):
@ -56,103 +105,140 @@ def decompress(P):
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx%q, By%q]
B = (Bx % q, By % q, 1, (Bx * By) % q)
ident = (0, 1, 1, 0)
def edwards(P, Q):
x1 = P[0]
y1 = P[1]
x2 = Q[0]
y2 = Q[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return [x3%q, y3%q]
def add(P, Q):
A = (P[1]-P[0])*(Q[1]-Q[0]) % q
B = (P[1]+P[0])*(Q[1]+Q[0]) % q
C = 2 * P[3] * Q[3] * d % q
D = 2 * P[2] * Q[2] % q
E = B-A
F = D-C
G = D+C
H = B+A
return (E*F, G*H, F*G, E*H)
def edwards_add(P, Q):
# This is formula sequence 'addition-add-2008-hwcd-3' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
(x2, y2, z2, t2) = Q
a = (y1-x1)*(y2-x2) % q
b = (y1+x1)*(y2+x2) % q
c = t1*2*d*t2 % q
dd = z1*2*z2 % q
e = b - a
f = dd - c
g = dd + c
h = b + a
x3 = e*f
y3 = g*h
t3 = e*h
z3 = f*g
return (x3 % q, y3 % q, z3 % q, t3 % q)
def edwards_double(P):
# This is formula sequence 'dbl-2008-hwcd' from
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
(x1, y1, z1, t1) = P
a = x1*x1 % q
b = y1*y1 % q
c = 2*z1*z1 % q
# dd = -a
e = ((x1+y1)*(x1+y1) - a - b) % q
g = -a + b # dd + b
f = g - c
h = -a - b # dd - b
x3 = e*f
y3 = g*h
t3 = e*h
z3 = f*g
return (x3 % q, y3 % q, z3 % q, t3 % q)
def add_compressed(P, Q):
return compress(add(decompress(P), decompress(Q)))
def scalarmult(P, e):
if e == 0: return [0, 1]
Q = scalarmult(P, e//2)
Q = edwards(Q, Q)
if e & 1: Q = edwards(Q, P)
if e == 0:
return ident
Q = scalarmult(P, e // 2)
Q = edwards_double(Q)
if e & 1:
Q = edwards_add(Q, P)
return Q
# Bpow[i] == scalarmult(B, 2**i)
Bpow = []
def make_Bpow():
P = B
for i in range(253):
Bpow.append(P)
P = edwards_double(P)
make_Bpow()
def scalarmult_B(e):
"""
Implements scalarmult(B, e) more efficiently.
"""
# scalarmult(B, l) is the identity
e = e % l
P = ident
for i in range(253):
if e & 1:
P = edwards_add(P, Bpow[i])
e = e // 2
assert e == 0, e
return P
def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
return b''.join([int2byte(sum([bits[i*8 + j] << j for j in range(8)])) for i in range(b//8)])
return b''.join([
int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b//8)
])
def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b-1)] + [x & 1]
return b''.join([int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b//8)])
(x, y, z, t) = P
zi = inv(z)
x = (x * zi) % q
y = (y * zi) % q
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return b''.join([
int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b // 8)
])
def bit(h, i):
return (indexbytes(h, i//8) >> (i%8)) & 1
return (indexbytes(h, i // 8) >> (i % 8)) & 1
def isoncurve(P):
x = P[0]
y = P[1]
return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0
(x, y, z, t) = P
return (z % q != 0 and
x*y % q == z*t % q and
(y*y - x*x - z*z - d*t*t) % q == 0)
def decodeint(s):
return sum(2**i * bit(s, i) for i in range(0, b))
return sum(2 ** i * bit(s, i) for i in range(0, b))
def decodepoint(s):
y = sum(2**i * bit(s, i) for i in range(0, b-1))
y = sum(2 ** i * bit(s, i) for i in range(0, b - 1))
x = xrecover(y)
if x & 1 != bit(s, b-1): x = q - x
P = [x, y]
if not isoncurve(P): raise Exception("decoding point that is not on curve")
if x & 1 != bit(s, b-1):
x = q - x
P = (x, y, 1, (x*y) % q)
if not isoncurve(P):
raise ValueError("decoding point that is not on curve")
return P
# These are unused but let's keep them
#def H(m):
# return hashlib.sha512(m).digest()
#
#def Hint(m):
# h = H(m)
# return sum(2**i * bit(h, i) for i in range(2*b))
#
#def publickey(sk):
# h = H(sk)
# a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2))
# A = scalarmult(B, a)
# return encodepoint(A)
#
#def signature(m, sk, pk):
# h = H(sk)
# a = 2**(b-2) + sum(2**i * bit(h, i) for i in range(3, b-2))
# r = Hint(intlist2bytes([indexbytes(h, j) for j in range(b//8, b//4)]) + m)
# R = scalarmult(B, r)
# S = (r + Hint(encodepoint(R)+pk+m) * a) % l
# return encodepoint(R) + encodeint(S)
#
#def checkvalid(s, m, pk):
# if len(s) != b//4: raise Exception("signature length is wrong")
# if len(pk) != b//8: raise Exception("public-key length is wrong")
# R = decodepoint(s[0:b//8])
# A = decodepoint(pk)
# S = decodeint(s[b//8:b//4])
# h = Hint(encodepoint(R) + pk + m)
# if scalarmult(B, S) != edwards(R, scalarmult(A, h)):
# raise Exception("signature does not pass verification")
def public_from_secret(k):
keyInt = decodeint(k)
aB = scalarmult(B, keyInt)
aB = scalarmult_B(keyInt)
return encodepoint(aB)
def public_from_secret_hex(hk):
return hexlify(public_from_secret(unhexlify(hk))).decode()
return binascii.hexlify(public_from_secret(binascii.unhexlify(hk))).decode()

View file

@ -219,9 +219,9 @@ class Wallet(object):
struct.pack('<I', major), struct.pack('<I', minor)])
m = keccak_256(hsdata).digest()
# D = master_psk + m * B
D = ed25519.add_compressed(
D = ed25519.edwards_add(
ed25519.decodepoint(master_psk),
ed25519.scalarmult(ed25519.B, ed25519.decodeint(m)))
ed25519.scalarmult_B(ed25519.decodeint(m)))
# C = master_svk * D
C = ed25519.scalarmult(D, ed25519.decodeint(master_svk))
netbyte = bytearray([