Introduce ed25519 from pynacl

pynacl
Michał Sałaban 2 years ago
parent 59953f6f81
commit 447e4b6fb5

@ -33,16 +33,8 @@ 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)
import nacl.bindings
b = 256
@ -50,36 +42,41 @@ 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 bit(h, i):
return (six.indexbytes(h, i // 8) >> (i % 8)) & 1
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 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
def decodeint(s):
return sum(2 ** i * bit(s, i) for i in range(0, b))
d = -121665 * inv(121666) % q
edwards_add = nacl.bindings.crypto_core_ed25519_add
inv = nacl.bindings.crypto_core_ed25519_scalar_invert
public_from_secret = nacl.bindings.crypto_sign_ed25519_sk_to_pk
scalar_reduce = nacl.bindings.crypto_core_ed25519_scalar_reduce
scalarmult_B = nacl.bindings.crypto_scalarmult_ed25519_base_noclamp
def scalarmult(P, e):
return nacl.bindings.crypto_scalarmult_ed25519_noclamp(e, P)
d = -121665 * decodeint(inv(encodeint(121666))) % q
I = pow(2, (q - 1) // 4, q)
def xrecover(y):
xx = (y * y - 1) * inv(d * y * y + 1)
xx = (y * y - 1) * decodeint(inv(encodeint(d * y * y + 1)))
x = pow(xx, (q + 3) // 8, q)
if (x * x - xx) % q != 0:
@ -91,113 +88,12 @@ def xrecover(y):
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)
By = 4 * decodeint(inv(encodeint(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)
@ -212,39 +108,20 @@ def encodepoint(P):
)
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))
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
def public_from_secret(k):
keyInt = decodeint(k)
aB = scalarmult_B(keyInt)
return encodepoint(aB)
def pad_to_64B(v):
return nacl.bindings.utils.sodium_pad(v, 64)
def public_from_secret_hex(hk):
return binascii.hexlify(public_from_secret(binascii.unhexlify(hk))).decode()
return binascii.hexlify(
public_from_secret(pad_to_64B(binascii.unhexlify(hk)))
).decode()

@ -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,10 +117,8 @@ 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 _sc_reduce(self, input):
return hexlify(ed25519.scalar_reduce(ed25519.pad_to_64B(input))).decode()
def hex_seed(self):
return self.hex
@ -131,7 +128,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 self._sc_reduce(a)
def secret_view_key(self):
b = (
@ -139,7 +136,7 @@ class Seed(object):
if self.is_mymonero()
else unhexlify(self.secret_spend_key())
)
return self.sc_reduce(keccak_256(b).digest())
return self._sc_reduce(keccak_256(b).digest())
def public_spend_key(self):
if self._ed_pub_spend_key:
@ -176,7 +173,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

@ -144,10 +144,8 @@ class Transaction(object):
for keyidx, tx_key in enumerate(self.pubkeys):
hsdata = b"".join(
[
ed25519.encodepoint(
ed25519.scalarmult(
ed25519.decodepoint(tx_key), ed25519.decodeint(svk) * 8
)
ed25519.scalarmult(
tx_key, ed25519.encodeint(ed25519.decodeint(svk) * 8)
),
varint.encode(idx),
]
@ -159,11 +157,9 @@ class Transaction(object):
Hsint = Hsint_ur % ed25519.l
Hs = ed25519.encodeint(Hsint)
k = ed25519.encodepoint(
ed25519.edwards_add(
ed25519.scalarmult_B(Hsint),
ed25519.decodepoint(psk),
)
k = ed25519.edwards_add(
ed25519.scalarmult_B(Hs),
psk,
)
if k != stealth_address:
continue

@ -231,15 +231,13 @@ 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))
)
D = ed25519.edwards_add(master_psk, ed25519.scalarmult_B(m))
# C = master_svk * D
C = ed25519.scalarmult(D, ed25519.decodeint(master_svk))
C = ed25519.scalarmult(D, master_svk)
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