Browse Source

py3port: Zcash and Monero-related changes

MMGen 6 years ago
parent
commit
53189f4469
12 changed files with 118 additions and 36 deletions
  1. 9 11
      mmgen/addr.py
  2. 1 6
      mmgen/ed25519.py
  3. 85 0
      mmgen/ed25519ll_djbec.py
  4. 4 4
      mmgen/main_addrgen.py
  5. 1 1
      mmgen/main_tool.py
  6. 1 0
      mmgen/obj.py
  7. 1 4
      mmgen/protocol.py
  8. 5 5
      mmgen/sha256.py
  9. 5 4
      mmgen/tool.py
  10. 1 1
      scripts/test-release.sh
  11. 1 0
      setup.py
  12. 4 0
      test/test.py

+ 9 - 11
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 <dholth@fastmail.fm> - 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

+ 1 - 6
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)])

+ 85 - 0
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 <dholth@fastmail.fm> - 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))

+ 4 - 4
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())
 

+ 1 - 1
mmgen/main_tool.py

@@ -141,7 +141,7 @@ Type '{pn} help <command> 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()
 

+ 1 - 0
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
 

+ 1 - 4
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])
 

+ 5 - 5
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

+ 5 - 4
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

+ 1 - 1
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"

+ 1 - 0
setup.py

@@ -115,6 +115,7 @@ setup(
 			'mmgen.common',
 			'mmgen.crypto',
 			'mmgen.ed25519',
+			'mmgen.ed25519ll_djbec',
 			'mmgen.exception',
 			'mmgen.filename',
 			'mmgen.globalvars',

+ 4 - 0
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')