Browse Source

minor changes and cleanups

MMGen 5 years ago
parent
commit
4d07d53ff6
9 changed files with 67 additions and 34 deletions
  1. 1 1
      mmgen/devtools.py
  2. 2 0
      mmgen/exception.py
  3. 14 6
      mmgen/filename.py
  4. 18 8
      mmgen/main_wallet.py
  5. 11 5
      mmgen/obj.py
  6. 14 11
      mmgen/seed.py
  7. 5 1
      mmgen/util.py
  8. 1 1
      test/objtest.py
  9. 1 1
      test/unit_tests_d/ut_subseed.py

+ 1 - 1
mmgen/devtools.py

@@ -6,7 +6,7 @@ from difflib import unified_diff
 
 def pmsg(*args,out=sys.stderr):
 	d = args if len(args) > 1 else '' if not args else args[0]
-	out.write(pprint.PrettyPrinter(indent=4,compact=True).pformat(d) + '\n')
+	out.write('\n' + pprint.PrettyPrinter(indent=4).pformat(d) + '\n')
 def pdie(*args,exit_val=1,out=sys.stderr):
 	pmsg(*args,out=out)
 	sys.exit(exit_val)

+ 2 - 0
mmgen/exception.py

@@ -24,10 +24,12 @@ mmgen.exception: Exception classes for the MMGen suite
 class UserNonConfirmation(Exception):     mmcode = 1
 class BadAgeFormat(Exception):            mmcode = 1
 class BadFilename(Exception):             mmcode = 1
+class BadFileExtension(Exception):        mmcode = 1
 class SocketError(Exception):             mmcode = 1
 class UserAddressNotInWallet(Exception):  mmcode = 1
 class MnemonicError(Exception):           mmcode = 1
 class RangeError(Exception):              mmcode = 1
+class FileNotFound(Exception):            mmcode = 1
 
 # 2: yellow hl, message only
 class InvalidTokenAddress(Exception):     mmcode = 2

+ 14 - 6
mmgen/filename.py

@@ -19,7 +19,10 @@
 """
 filename.py:  Filename class and methods for the MMGen suite
 """
+
 import sys,os
+
+from mmgen.exception import BadFileExtension,FileNotFound
 from mmgen.obj import *
 from mmgen.util import die,get_extension
 from mmgen.seed import *
@@ -51,11 +54,16 @@ class Filename(MMGenObject):
 			# TODO: other file types
 			self.ftype = SeedSource.ext_to_type(self.ext)
 			if not self.ftype:
-				die(3,"'{}': not a recognized extension for SeedSource".format(self.ext))
+				m = "'{}': not a recognized SeedSource file extension".format(self.ext)
+				raise BadFileExtension(m)
 
+		try:
+			st = os.stat(fn)
+		except:
+			raise FileNotFound('{!r}: file not found'.format(fn))
 
 		import stat
-		if stat.S_ISBLK(os.stat(fn).st_mode):
+		if stat.S_ISBLK(st.st_mode):
 			mode = (os.O_RDONLY,os.O_RDWR)[bool(write)]
 			if g.platform == 'win': mode |= os.O_BINARY
 			try:
@@ -68,10 +76,10 @@ class Filename(MMGenObject):
 				self.size = os.lseek(fd, 0, os.SEEK_END)
 				os.close(fd)
 		else:
-			self.size = os.stat(fn).st_size
-			self.mtime = os.stat(fn).st_mtime
-			self.ctime = os.stat(fn).st_ctime
-			self.atime = os.stat(fn).st_atime
+			self.size  = st.st_size
+			self.mtime = st.st_mtime
+			self.ctime = st.st_ctime
+			self.atime = st.st_atime
 
 class MMGenFileList(list,MMGenObject):
 

+ 18 - 8
mmgen/main_wallet.py

@@ -66,6 +66,8 @@ elif invoked_as == 'subgen':
 	desc = 'Generate a subwallet from ' + dsw
 	opt_filter = 'dehHiJkKlLmoOpPqrSvz-' # omitted: f
 	usage = '[opts] [infile] <Subseed Index>'
+	iaction = 'input'
+	oaction = 'output'
 	do_sw_note = True
 
 opts_data = {
@@ -136,16 +138,24 @@ if invoked_as == 'subgen':
 	from mmgen.obj import SubSeedIdx
 	ss_idx = SubSeedIdx(cmd_args.pop())
 
-sf = get_seed_file(cmd_args,nargs,invoked_as=invoked_as)
+if cmd_args:
+	if invoked_as == 'gen' or len(cmd_args) > 1:
+		opts.usage()
+	check_infile(cmd_args[0])
 
-if not invoked_as == 'chk': do_license_msg()
+sf = get_seed_file(cmd_args,nargs,invoked_as=invoked_as)
 
-if invoked_as in ('conv','passchg','subgen'):
-	m1 = green('Processing input wallet')
-	m2 = yellow(' (default wallet)') if sf and os.path.dirname(sf) == g.data_dir else ''
-	msg(m1+m2)
+if invoked_as != 'chk':
+	do_license_msg()
 
-ss_in = None if invoked_as == 'gen' else SeedSource(sf,passchg=(invoked_as=='passchg'))
+if invoked_as == 'gen':
+	ss_in = None
+else:
+	ss_in = SeedSource(sf,passchg=(invoked_as=='passchg'))
+	m1 = green('Processing input wallet ')
+	m2 = ss_in.seed.sid.hl()
+	m3 = yellow(' (default wallet)') if sf and os.path.dirname(sf) == g.data_dir else ''
+	msg(m1+m2+m3)
 
 if invoked_as == 'chk':
 	lbl = ss_in.ssdata.label.hl() if hasattr(ss_in.ssdata,'label') else 'NONE'
@@ -153,7 +163,7 @@ if invoked_as == 'chk':
 	# TODO: display creation date
 	sys.exit(0)
 
-if invoked_as in ('conv','passchg','subgen'):
+if invoked_as != 'gen':
 	gmsg('Processing output wallet')
 
 if invoked_as == 'subgen':

+ 11 - 5
mmgen/obj.py

@@ -169,11 +169,15 @@ class Hilite(object):
 		return self.fmtc(self,*args,**kwargs)
 
 	@classmethod
-	def hlc(cls,s,color=True):
+	def hlc(cls,s,color=True,encl=''):
+		if encl:
+			assert isinstance(encl,str) and len(encl) == 2, "'encl' must be 2-character str"
+			s = encl[0] + s + encl[1]
 		return cls.colorize(s,color=color)
 
-	def hl(self,color=True):
-		return self.colorize(self,color=color)
+	def hl(self,*args,**kwargs):
+		assert args == () # forbid invocation w/o keywords
+		return self.hlc(self,*args,**kwargs)
 
 	def __str__(self):
 		return self.colorize(self,color=False)
@@ -816,9 +820,11 @@ class MMGenPWIDString(MMGenLabel):
 	min_len = 1
 	desc = 'password ID string'
 	forbidden = list(' :/\\')
+	trunc_ok = False
+
 
-class SeedShareIDString(MMGenPWIDString):
-	desc = 'seed share ID string'
+class SeedSplitIDString(MMGenPWIDString):
+	desc = 'seed split ID string'
 
 class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
 	width = 1

+ 14 - 11
mmgen/seed.py

@@ -50,8 +50,8 @@ def is_mmgen_mnemonic(s): return _is_mnemonic(s,fmt='words')
 
 class SeedBase(MMGenObject):
 
-	data    = MMGenImmutableAttr('data',bytes,typeconv=False)
-	sid     = MMGenImmutableAttr('sid',SeedID,typeconv=False)
+	data = MMGenImmutableAttr('data',bytes,typeconv=False)
+	sid  = MMGenImmutableAttr('sid',SeedID,typeconv=False)
 
 	def __init__(self,seed_bin=None):
 		if not seed_bin:
@@ -60,8 +60,8 @@ class SeedBase(MMGenObject):
 		elif len(seed_bin)*8 not in g.seed_lens:
 			die(3,'{}: invalid seed length'.format(len(seed_bin)))
 
-		self.data      = seed_bin
-		self.sid       = SeedID(seed=self)
+		self.data = seed_bin
+		self.sid  = SeedID(seed=self)
 
 	@property
 	def bitlen(self):
@@ -176,11 +176,12 @@ class SubSeedList(MMGenObject):
 		def add_subseed(idx,length):
 			for nonce in range(self.nonce_start,self.member_type.max_nonce+1): # handle SeedID collisions
 				sid = make_chksum_8(self.member_type.make_subseed_bin(self,idx,nonce,length))
-				if not (sid in self.data['long'] or sid in self.data['short'] or sid == self.parent_seed.sid):
+				if sid in self.data['long'] or sid in self.data['short'] or sid == self.parent_seed.sid:
+					if g.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds
+						self._collision_debug_msg(sid,idx,nonce)
+				else:
 					self.data[length][sid] = (idx,nonce)
 					return last_sid == sid
-				elif g.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds
-					self._collision_debug_msg(sid,idx,nonce)
 			else: # must exit here, as this could leave self.data in inconsistent state
 				raise SubSeedNonceRangeExceeded('add_subseed(): nonce range exceeded')
 
@@ -279,7 +280,7 @@ class SeedShareList(SubSeedList):
 	split_type = 'N-of-N'
 
 	count = MMGenImmutableAttr('count',SeedShareCount)
-	id_str = MMGenImmutableAttr('id_str',SeedShareIDString)
+	id_str = MMGenImmutableAttr('id_str',SeedSplitIDString)
 
 	def __init__(self,parent_seed,count,id_str=None,master_idx=None):
 		self.member_type = SeedShare
@@ -307,12 +308,12 @@ class SeedShareList(SubSeedList):
 				self.data['long'][self.master_share.sid] = (1,self.master_share.nonce)
 			self._generate(count-1)
 			self.last_share = ls = SeedShareLast(self)
-			if ls.sid in self.data['long'].keys + [parent_seed.sid]:
+			if ls.sid in self.data['long'] or ls.sid == parent_seed.sid:
 				# collision: throw out entire split list and redo with new start nonce
 				if g.debug_subseed:
 					self._collision_debug_msg(ls.sid,count,nonce,nonce_desc='nonce_start')
 			else:
-				self.data['long'][ls.sid] = (self.count,nonce)
+				self.data['long'][ls.sid] = (count,nonce)
 				break
 		else:
 			raise SubSeedNonceRangeExceeded('nonce range exceeded')
@@ -419,6 +420,8 @@ class SeedShareMaster(SeedBase):
 		scramble_key = b'master_share:' + self.idx.to_bytes(2,'big') + self.nonce.to_bytes(2,'big')
 		return scramble_seed(seed.data,scramble_key)[:seed.byte_len]
 
+	# Don't bother with avoiding seed ID collision here, as sid of derived seed is not used
+	# by user as an identifier
 	def make_derived_seed_bin(self,id_str,count):
 		# field maximums: id_str: none (256 chars), count: 65535 (1024)
 		scramble_key = id_str.encode() + b':' + count.to_bytes(2,'big')
@@ -426,7 +429,7 @@ class SeedShareMaster(SeedBase):
 
 class SeedShareMasterJoining(SeedShareMaster):
 
-	id_str = MMGenImmutableAttr('id_str',SeedShareIDString)
+	id_str = MMGenImmutableAttr('id_str',SeedSplitIDString)
 	count = MMGenImmutableAttr('count',SeedShareCount)
 
 	def __init__(self,idx,base_seed,id_str,count):

+ 5 - 1
mmgen/util.py

@@ -503,7 +503,7 @@ def check_file_type_and_access(fname,ftype,blkdev_ok=False):
 
 	try: mode = os.stat(fname).st_mode
 	except:
-		die(1,"Unable to stat requested {} '{}'".format(ftype,fname))
+		raise FileNotFound("Requested {} '{}' not found".format(ftype,fname))
 
 	for t in ok_types:
 		if t[0](mode): break
@@ -521,6 +521,10 @@ def check_outfile(f,blkdev_ok=False):
 	return check_file_type_and_access(f,'output file',blkdev_ok=blkdev_ok)
 def check_outdir(f):
 	return check_file_type_and_access(f,'output directory')
+def check_wallet_extension(fn):
+	from mmgen.seed import SeedSource
+	if not SeedSource.ext_to_type(get_extension(fn)):
+		raise BadFileExtension("'{}': unrecognized seed source file extension".format(fn))
 def make_full_path(outdir,outfile):
 	return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
 

+ 1 - 1
test/objtest.py

@@ -119,7 +119,7 @@ def do_loop():
 	network = ('mainnet','testnet')[bool(g.testnet)]
 	gl = globals()
 	exec('from test.objtest_py_d.ot_{}_{} import tests'.format(g.coin.lower(),network),gl,gl)
-	gmsg('Running data objest tests for {} {}'.format(g.coin,network))
+	gmsg('Running data object tests for {} {}'.format(g.coin,network))
 	clr = None
 	for test in tests:
 		if utests and test not in utests: continue

+ 1 - 1
test/unit_tests_d/ut_subseed.py

@@ -155,7 +155,7 @@ class unit_test(object):
 
 			msg_r('Testing Seed ID collisions ({} subseed pairs)...'.format(ss_count))
 
-			seed_bin = bytes.fromhex('12abcdef' * 8)
+			seed_bin = bytes.fromhex('12abcdef' * 8) # 95B3D78D
 			seed = Seed(seed_bin)
 
 			seed.subseeds._generate(ss_count)