Compare commits

...

8 Commits

Author SHA1 Message Date
Michał Sałaban 752496be6c
Update donation address
2 years ago
Michał Sałaban 165d1b597c Bump up version
2 years ago
Michał Sałaban 25b1a9dc89 Remove last pieces of code from the reference implementation of ed25519
2 years ago
Michał Sałaban d3a6a8f470 Optimize calculations
2 years ago
Michał Sałaban 9c594e0692 Merge branch 'master' into pynacl
2 years ago
Michał Sałaban 1f09c596b5 Replace ed25519 Python implementation with pynacl
2 years ago
Michał Sałaban ca87a863a8 Add tests on scalar reduce
2 years ago
Michał Sałaban 447e4b6fb5 Introduce ed25519 from pynacl
2 years ago

@ -1,5 +1,9 @@
[bumpversion]
current_version = 0.9.3
current_version = 0.99
parse = (?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?
serialize =
{major}.{minor}.{patch}
{major}.{minor}
[bumpversion:file:README.rst]

@ -14,7 +14,7 @@ Python Monero module
A comprehensive Python module for handling Monero cryptocurrency.
* release 0.9.3
* release 0.99
* open source: https://github.com/monero-ecosystem/monero-python
* works with Monero 0.13.x and `the latest source`_ (at least we try to keep up)
* Python 2.x and 3.x compatible
@ -41,13 +41,10 @@ Copyright (c) 2017-2018 Michał Sałaban <michal@salaban.info> and Contributors:
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
@ -66,7 +63,7 @@ Want to help?
-------------
If you find this project useful, please consider a donation to the following address:
``481SgRxo8hwBCY4z6r88JrN5X8JFCJYuJUDuJXGybTwaVKyoJPKoGj3hQRAEGgQTdmV1xH1URdnHkJv6He5WkEbq6iKhr94``
``83bK2pMxCQXdRyd6W1haNWYRsF6Qb3iGa8gxKEynm9U7cYoXrMHFwRrFFuxRSgnLtGe7LM8SmrPY6L3TVBa3UV3YLuVJ7Rw``
Development

@ -3,14 +3,12 @@ Authors
* Michał Sałaban <michal@salaban.info>
* MoneroPy Developers (``monero/base58.py`` taken from `MoneroPy`_)
* `pyca/ed25519`_ Developers (``monero/ed25519.py``)
* thomasv@gitorious (``monero/seed.py`` based on `Electrum`_)
* and other Contributors: `lalanza808`_, `cryptochangements34`_, `atward`_, `rooterkyberian`_, `brucexiu`_, `lialsoftlab`_, `moneroexamples`_, `massanchik`_, `MrClottom`_, `jeffro256`_, `sometato`_.
.. _`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

@ -56,9 +56,9 @@ author = "Michal Salaban"
# built documents.
#
# The short X.Y version.
version = "0.9.3"
version = "0.99"
# The full version, including alpha/beta/rc tags.
release = "0.9.3"
release = "0.99"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

@ -1,6 +1,19 @@
Release Notes
=============
0.99
----
This is a test release before 1.0. The reference library for Ed25519 cryptography has been dropped
and replaced with `pynacl`_ which is a wrapper over `libsodium`_, the industry standard
lightning-fast C library.
There are no backward-incompatible changes in the API. The aim is to have the software tested
thoroughly before the first stable release.
.. _`pynacl`: https://github.com/pyca/pynacl/
.. _`libsodium`: https://github.com/jedisct1/libsodium/
0.9
---

@ -1,3 +1,3 @@
from . import address, account, const, daemon, wallet, numbers, wordlists, seed
__version__ = "0.9.3"
__version__ = "0.99"

@ -95,14 +95,20 @@ class Address(BaseAddress):
:rtype: bool
"""
return ed25519.public_from_secret_hex(key) == self.view_key()
try:
return ed25519.public_from_secret_hex(key) == self.view_key()
except ValueError:
return False
def check_private_spend_key(self, key):
"""Checks if private spend key matches this address.
:rtype: bool
"""
return ed25519.public_from_secret_hex(key) == self.spend_key()
try:
return ed25519.public_from_secret_hex(key) == self.spend_key()
except ValueError:
return False
def with_payment_id(self, payment_id=0):
"""Integrates payment id into the address.

@ -1,250 +1,19 @@
# ed25519.py - Optimized version of the reference implementation of Ed25519
#
# 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/>.
"""
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.
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 six
import sys
if sys.version_info >= (3,): # pragma: no cover
intlist2bytes = bytes
else: # pragma: no cover
range = xrange
def intlist2bytes(l):
return b"".join(chr(c) for c in l)
b = 256
q = 2 ** 255 - 19
l = 2 ** 252 + 27742317777372353535851937790883648493
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)
def xrecover(y):
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):
zinv = inv(P[2])
return (P[0] * zinv % q, P[1] * zinv % q)
def decompress(P):
return (P[0], P[1], 1, P[0] * P[1] % q)
By = 4 * inv(5)
Bx = xrecover(By)
B = (Bx % q, By % q, 1, (Bx * By) % q)
ident = (0, 1, 1, 0)
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 scalarmult(P, e):
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(
[
six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b // 8)
]
)
def encodepoint(P):
(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(
[
six.int2byte(sum([bits[i * 8 + j] << j for j in range(8)]))
for i in range(b // 8)
]
)
def bit(h, i):
return (six.indexbytes(h, i // 8) >> (i % 8)) & 1
def isoncurve(P):
(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))
import nacl.bindings
def decodepoint(s):
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, 1, (x * y) % q)
if not isoncurve(P):
raise ValueError("decoding point that is not on curve")
return P
edwards_add = nacl.bindings.crypto_core_ed25519_add
inv = nacl.bindings.crypto_core_ed25519_scalar_invert
scalar_add = nacl.bindings.crypto_core_ed25519_scalar_add
scalarmult_B = nacl.bindings.crypto_scalarmult_ed25519_base_noclamp
scalarmult = nacl.bindings.crypto_scalarmult_ed25519_noclamp
def public_from_secret(k):
keyInt = decodeint(k)
aB = scalarmult_B(keyInt)
return encodepoint(aB)
def scalar_reduce(v):
return nacl.bindings.crypto_core_ed25519_scalar_reduce(v + (64 - len(v)) * b"\0")
def public_from_secret_hex(hk):
return binascii.hexlify(public_from_secret(binascii.unhexlify(hk))).decode()
try:
return binascii.hexlify(scalarmult_B(binascii.unhexlify(hk))).decode()
except nacl.exceptions.RuntimeError:
raise ValueError("Invalid secret key")

@ -37,7 +37,6 @@
from binascii import hexlify, unhexlify
from os import urandom
import warnings
from . import base58, const, ed25519, wordlists
from .address import address
from .keccak import keccak_256
@ -93,7 +92,7 @@ class Seed(object):
"Not valid mnemonic phrase or hex: {arg}".format(arg=phrase_or_hex)
)
else:
self.hex = generate_hex()
self.hex = generate_random_hex()
self._encode_seed()
def is_mymonero(self):
@ -118,11 +117,6 @@ class Seed(object):
return True
raise ValueError("Invalid checksum")
def sc_reduce(self, input):
integer = ed25519.decodeint(input)
modulo = integer % ed25519.l
return hexlify(ed25519.encodeint(modulo)).decode()
def hex_seed(self):
return self.hex
@ -131,7 +125,7 @@ class Seed(object):
def secret_spend_key(self):
a = self._hex_seed_keccak() if self.is_mymonero() else unhexlify(self.hex)
return self.sc_reduce(a)
return hexlify(ed25519.scalar_reduce(a)).decode()
def secret_view_key(self):
b = (
@ -139,7 +133,7 @@ class Seed(object):
if self.is_mymonero()
else unhexlify(self.secret_spend_key())
)
return self.sc_reduce(keccak_256(b).digest())
return hexlify(ed25519.scalar_reduce(keccak_256(b).digest())).decode()
def public_spend_key(self):
if self._ed_pub_spend_key:
@ -176,7 +170,7 @@ class Seed(object):
return address(base58.encode(data + checksum[0:8]))
def generate_hex(n_bytes=32):
def generate_random_hex(n_bytes=32):
"""Generate a secure and random hexadecimal string. 32 bytes by default, but arguments can override.
:rtype: str

@ -142,28 +142,22 @@ class Transaction(object):
def _scan_pubkeys(svk, psk, stealth_address, amount, encamount):
for keyidx, tx_key in enumerate(self.pubkeys):
# precompute
svk_2 = ed25519.scalar_add(svk, svk)
svk_4 = ed25519.scalar_add(svk_2, svk_2)
svk_8 = ed25519.scalar_add(svk_4, svk_4)
#
hsdata = b"".join(
[
ed25519.encodepoint(
ed25519.scalarmult(
ed25519.decodepoint(tx_key), ed25519.decodeint(svk) * 8
)
),
ed25519.scalarmult(svk_8, tx_key),
varint.encode(idx),
]
)
Hs_ur = keccak_256(hsdata).digest()
# sc_reduce32:
Hsint_ur = ed25519.decodeint(Hs_ur)
Hsint = Hsint_ur % ed25519.l
Hs = ed25519.encodeint(Hsint)
k = ed25519.encodepoint(
ed25519.edwards_add(
ed25519.scalarmult_B(Hsint),
ed25519.decodepoint(psk),
)
Hs = ed25519.scalar_reduce(Hs_ur)
k = ed25519.edwards_add(
ed25519.scalarmult_B(Hs),
psk,
)
if k != stealth_address:
continue

@ -232,14 +232,14 @@ class Wallet(object):
m = keccak_256(hsdata).digest()
# D = master_psk + m * B
D = ed25519.edwards_add(
ed25519.decodepoint(master_psk), ed25519.scalarmult_B(ed25519.decodeint(m))
master_psk, ed25519.scalarmult_B(ed25519.scalar_reduce(m))
)
# C = master_svk * D
C = ed25519.scalarmult(D, ed25519.decodeint(master_svk))
C = ed25519.scalarmult(master_svk, D)
netbyte = bytearray(
[const.SUBADDR_NETBYTES[const.NETS.index(master_address.net)]]
)
data = netbyte + ed25519.encodepoint(D) + ed25519.encodepoint(C)
data = netbyte + D + C
checksum = keccak_256(data).digest()[:4]
return address.SubAddress(base58.encode(hexlify(data + checksum)))

@ -1,4 +1,5 @@
pycryptodomex~=3.10
pynacl~=1.4.0
requests
six>=1.12.0
ipaddress

Loading…
Cancel
Save