diff --git a/mmgen/addr.py b/mmgen/addr.py index 0f3ae9d9..a524f1aa 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -99,11 +99,11 @@ class AddrGeneratorZcashZ(AddrGenerator): vk_width = 97 def zhash256(self,s,t): - s = list(map(ord,s+'\0'*32)) + s = bytearray(s + bytes(32)) s[0] |= 0xc0 s[32] = t from mmgen.sha256 import Sha256 - return Sha256(list(map(chr,s)),preprocess=False).digest() + return Sha256(s,preprocess=False).digest() def to_addr(self,pubhex): # pubhex is really privhex key = unhexlify(pubhex) @@ -120,12 +120,12 @@ class AddrGeneratorZcashZ(AddrGenerator): def to_viewkey(self,pubhex): # pubhex is really privhex key = unhexlify(pubhex) assert len(key) == 32,'{}: incorrect privkey length'.format(len(key)) - vk = list(map(ord,self.zhash256(key,0)+self.zhash256(key,1))) + vk = bytearray(self.zhash256(key,0)+self.zhash256(key,1)) vk[32] &= 0xf8 vk[63] &= 0x7f vk[63] |= 0x40 from mmgen.protocol import _b58chk_encode - ret = _b58chk_encode(g.proto.addr_ver_num['viewkey'][0] + hexlify(''.join(map(chr,vk)))) + ret = _b58chk_encode(g.proto.addr_ver_num['viewkey'][0] + hexlify(vk)) assert len(ret) == self.vk_width,'Invalid Zcash view key length' return ZcashViewKey(ret) @@ -142,13 +142,11 @@ class AddrGeneratorMonero(AddrGenerator): def to_addr(self,sk_hex): # sk_hex instead of pubhex - # ed25519ll, a low-level ctypes wrapper for Ed25519 digital signatures by - # Daniel Holth - http://bitbucket.org/dholth/ed25519ll/ - try: - assert not opt.use_internal_ed25519_mod - from ed25519ll.djbec import scalarmult,edwards,encodepoint,B - except: - from mmgen.ed25519 import scalarmult,edwards,encodepoint,B + if opt.use_old_ed25519: + from mmgen.ed25519 import edwards,encodepoint,B,scalarmult + else: + from mmgen.ed25519ll_djbec import scalarmult + from mmgen.ed25519 import edwards,encodepoint,B # Source and license for scalarmultbase function: # https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py diff --git a/mmgen/ed25519.py b/mmgen/ed25519.py index 0650959c..4a40f676 100755 --- a/mmgen/ed25519.py +++ b/mmgen/ed25519.py @@ -2,15 +2,10 @@ # Source: https://ed25519.cr.yp.to/python/ed25519.py # Date accessed: 2 Nov. 2016 -import hashlib - b = 256 q = 2**255 - 19 l = 2**252 + 27742317777372353535851937790883648493 -def H(m): - return hashlib.sha512(m).digest() - def expmod(b, e, m): if e == 0: return 1 t = expmod(b, e//2, m)**2 % m @@ -54,4 +49,4 @@ def encodepoint(P): x = P[0] y = P[1] bits = [(y >> i) & 1 for i in range(b-1)] + [x & 1] - return b''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b//8)]) + return bytes([sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(b//8)]) diff --git a/mmgen/ed25519ll_djbec.py b/mmgen/ed25519ll_djbec.py new file mode 100644 index 00000000..8971635c --- /dev/null +++ b/mmgen/ed25519ll_djbec.py @@ -0,0 +1,85 @@ +# Ported to Python 3 (added floor division) from ed25519ll package: +# https://pypi.org/project/ed25519ll/ +# This module is adapted from that package's pure-Python fallback +# implementation. All functions and vars not required by MMGen have +# been removed. +# +# ed25519ll, a low-level ctypes wrapper for Ed25519 digital signatures by +# Daniel Holth - http://bitbucket.org/dholth/ed25519ll/ +# This wrapper also contains a reasonably performant pure-Python fallback. +# Unlike the reference implementation, the Python implementation does not +# contain protection against timing attacks. +# +# Ed25519 digital signatures +# Based on http://ed25519.cr.yp.to/python/ed25519.py +# See also http://ed25519.cr.yp.to/software.html +# Adapted by Ron Garret +# Sped up considerably using coordinate transforms found on: +# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html +# Specifically add-2008-hwcd-4 and dbl-2008-hwcd + +q = 2**255 - 19 + +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 + +# Can probably get some extra speedup here by replacing this with +# an extended-euclidean, but performance seems OK without that +def inv(x): + return expmod(x,q-2,q) + +# Faster (!) version based on: +# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + +def xpt_add(pt1, pt2): + (X1, Y1, Z1, T1) = pt1 + (X2, Y2, Z2, T2) = pt2 + A = ((Y1-X1)*(Y2+X2)) % q + B = ((Y1+X1)*(Y2-X2)) % q + C = (Z1*2*T2) % q + D = (T1*2*Z2) % q + E = (D+C) % q + F = (B-A) % q + G = (B+A) % q + H = (D-C) % q + X3 = (E*F) % q + Y3 = (G*H) % q + Z3 = (F*G) % q + T3 = (E*H) % q + return (X3, Y3, Z3, T3) + +def xpt_double (pt): + (X1, Y1, Z1, _) = pt + A = (X1*X1) + B = (Y1*Y1) + C = (2*Z1*Z1) + D = (-A) % q + J = (X1+Y1) % q + E = (J*J-A-B) % q + G = (D+B) % q + F = (G-C) % q + H = (D-B) % q + X3 = (E*F) % q + Y3 = (G*H) % q + Z3 = (F*G) % q + T3 = (E*H) % q + return (X3, Y3, Z3, T3) + +def pt_xform (pt): + (x, y) = pt + return (x, y, 1, (x*y)%q) + +def pt_unxform (pt): + (x, y, z, _) = pt + return ((x*inv(z))%q, (y*inv(z))%q) + +def xpt_mult (pt, n): + if n==0: return pt_xform((0,1)) + _ = xpt_double(xpt_mult(pt, n>>1)) + return xpt_add(_, pt) if n&1 else _ + +def scalarmult(pt, e): + return pt_unxform(xpt_mult(pt_xform(pt), e)) diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index 1dc06b01..92d43b4b 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -53,8 +53,8 @@ opts_data = lambda: { -c, --print-checksum Print address list checksum and exit -d, --outdir= d Output files to directory 'd' instead of working dir -e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry --E, --use-internal-ed25519-mod Use (slow) internal ed25519 module for Monero - address generation, even if ed25519ll is installed +-E, --use-old-ed25519 Use original (and slow) ed25519 module for Monero + address generation instead of ed25519ll_djbec -i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below) -H, --hidden-incog-input-params=f,o Read hidden incognito data from file 'f' at offset 'o' (comma-separated) @@ -123,8 +123,8 @@ addr_type = MAT(opt.type or g.proto.dfl_mmtype,errmsg=errmsg) if len(cmd_args) < 1: opts.usage() -if opt.use_internal_ed25519_mod: - msg('Using (slow) internal ed25519 module by user request') +if opt.use_old_ed25519: + msg('Using old (slow) ed25519 module by user request') idxs = AddrIdxList(fmt_str=cmd_args.pop()) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index 8c7d812f..063e1276 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -141,7 +141,7 @@ Type '{pn} help for help on a particular command ) } -cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_internal_ed25519_mod']) +cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','use_old_ed25519']) if len(cmd_args) < 1: opts.usage() diff --git a/mmgen/obj.py b/mmgen/obj.py index 64b6090e..3d4594f9 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -397,6 +397,7 @@ class BTCAmt(Decimal,Hilite,InitErrors): class BCHAmt(BTCAmt): pass class B2XAmt(BTCAmt): pass class LTCAmt(BTCAmt): max_amt = 84000000 +class XMRAmt(BTCAmt): min_coin_unit = Decimal('0.000000000001') from mmgen.altcoins.eth.obj import ETHAmt,ETHNonce diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 4c13b4bd..f8c207b0 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -402,10 +402,7 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen): @classmethod def preprocess_key(cls,hexpriv,pubkey_type): # reduce key - try: - from ed25519ll.djbec import l - except: - from mmgen.ed25519 import l + from mmgen.ed25519 import l n = int(hexlify(unhexlify(hexpriv)[::-1]),16) % l return hexlify(unhexlify('{:064x}'.format(n))[::-1]) diff --git a/mmgen/sha256.py b/mmgen/sha256.py index 93e36c2e..aaff89f6 100755 --- a/mmgen/sha256.py +++ b/mmgen/sha256.py @@ -47,7 +47,7 @@ class Sha256(object): n,nPrime = 2,0 while nPrime < 64: if isPrime(n): - k[nPrime] = getFractionalBits(math.pow(n, 1.0 / 3)) + k[nPrime] = getFractionalBits(math.pow(n, 1 / 3)) nPrime += 1 n += 1 @@ -73,21 +73,21 @@ class Sha256(object): return hexlify(self.digest()) def bytesToWords(self): - assert type(self.M) in (str,list) + assert type(self.M) in (bytes,bytearray,list) words = [0] * (len(self.M) // 4 + len(self.M) % 4) b = 0 for i in range(len(self.M)): - words[b >> 5] |= ord(self.M[i]) << (24 - b % 32) + words[b >> 5] |= self.M[i] << (24 - b % 32) b += 8 self.M = words def wordsToBytes(self): assert type(self.M) == list and len(self.M) == 8 - self.M = ''.join([chr((self.M[b >> 5] >> (24 - b % 32)) & 0xff) for b in range(0,len(self.M)*32,8)]) + self.M = bytes([(self.M[b >> 5] >> (24 - b % 32) & 0xff) for b in range(0,len(self.M)*32,8)]) def preprocessBlock(self): def lshift(a,b): return (a << b) & 0xffffffff - assert type(self.M) == str + assert type(self.M) in (bytes,bytearray) l = len(self.M) * 8 self.bytesToWords() last_idx = lshift((l + 64 >> 9),4) + 15 diff --git a/mmgen/tool.py b/mmgen/tool.py index 419fc98f..1e0302d5 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -624,14 +624,15 @@ def monero_wallet_ops(infile,op,blockheight=None,addrs=None): gmsg('\n{} wallet{} {}ed'.format(dl,suf(dl),m[op][0].lower())) if op == 'sync': col1_w = max(list(map(len,bals))) + 1 - fs = '{:%s} {:18} {:18}' % col1_w - msg('\n'+fs.format('Wallet',' Balance',' Unlocked Balance')) + fs = '{:%s} {} {}' % col1_w + msg('\n'+fs.format('Wallet','Balance ','Unlocked Balance ')) tbals = [Decimal('0'),Decimal('0')] + from mmgen.obj import XMRAmt for bal in bals: for i in (0,1): tbals[i] += bals[bal][i] - msg(fs.format(bal+':',*bals[bal])) + msg(fs.format(bal+':',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in bals[bal]])) msg(fs.format('-'*col1_w,'-'*18,'-'*18)) - msg(fs.format('TOTAL:',*tbals)) + msg(fs.format('TOTAL:',*[XMRAmt(b).fmt(fs='5.12',color=True) for b in tbals])) os.environ['LANG'] = 'C' import pexpect diff --git a/scripts/test-release.sh b/scripts/test-release.sh index 0b29753b..71adf529 100755 --- a/scripts/test-release.sh +++ b/scripts/test-release.sh @@ -193,7 +193,7 @@ t_monero=( "mmgen-walletgen -q -r0 -p1 -Llabel --outdir $TMPDIR -o words" "$mmgen_keygen -q --accept-defaults --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs" 'cs1=$(mmgen-tool -q --accept-defaults --coin=xmr keyaddrfile_chksum $TMPDIR/*-XMR*.akeys)' -"$mmgen_keygen -q --use-internal-ed25519-mod --accept-defaults --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs" +"$mmgen_keygen -q --use-old-ed25519 --accept-defaults --outdir $TMPDIR --coin=xmr $TMPDIR/*.mmwords $monero_addrs" 'cs2=$(mmgen-tool -q --accept-defaults --coin=xmr keyaddrfile_chksum $TMPDIR/*-XMR*.akeys)' '[ "$cs1" == "$cs2" ] || false' "$mmgen_tool -q --accept-defaults --outdir $TMPDIR keyaddrlist2monerowallets $TMPDIR/*-XMR*.akeys addrs=23" diff --git a/setup.py b/setup.py index fb9897b8..28dabce1 100755 --- a/setup.py +++ b/setup.py @@ -115,6 +115,7 @@ setup( 'mmgen.common', 'mmgen.crypto', 'mmgen.ed25519', + 'mmgen.ed25519ll_djbec', 'mmgen.exception', 'mmgen.filename', 'mmgen.globalvars', diff --git a/test/test.py b/test/test.py index a6d10325..7dd5f2c8 100755 --- a/test/test.py +++ b/test/test.py @@ -1069,6 +1069,8 @@ cmd_group['ref_alt'] = ( ('ref_addrfile_gen_zec', 'generate address file (ZEC-T)'), ('ref_addrfile_gen_zec_z','generate address file (ZEC-Z)'), ('ref_addrfile_gen_xmr', 'generate address file (XMR)'), + # we test the old ed25519 library in test-release.sh, so skip this +# ('ref_addrfile_gen_xmr_old','generate address file (XMR - old (slow) ed25519 library)'), ('ref_keyaddrfile_gen_eth', 'generate key-address file (ETH)'), ('ref_keyaddrfile_gen_etc', 'generate key-address file (ETC)'), @@ -2609,6 +2611,8 @@ class MMGenTestSuite(object): def ref_addrfile_gen_xmr(self,name): self.ref_altcoin_addrgen(name,coin='XMR',mmtype='monero') + def ref_addrfile_gen_xmr_old(self,name): + self.ref_altcoin_addrgen(name,coin='XMR',mmtype='monero',add_args=['--use-old-ed25519']) def ref_keyaddrfile_gen_eth(self,name): self.ref_altcoin_addrgen(name,coin='ETH',mmtype='ethereum',gen_what='key')