From 4d07d53ff6cba4d678d68f1cebf7e8b81dbd3b10 Mon Sep 17 00:00:00 2001 From: MMGen Date: Sun, 13 Oct 2019 17:30:54 +0000 Subject: [PATCH] minor changes and cleanups --- mmgen/devtools.py | 2 +- mmgen/exception.py | 2 ++ mmgen/filename.py | 20 ++++++++++++++------ mmgen/main_wallet.py | 26 ++++++++++++++++++-------- mmgen/obj.py | 16 +++++++++++----- mmgen/seed.py | 25 ++++++++++++++----------- mmgen/util.py | 6 +++++- test/objtest.py | 2 +- test/unit_tests_d/ut_subseed.py | 2 +- 9 files changed, 67 insertions(+), 34 deletions(-) diff --git a/mmgen/devtools.py b/mmgen/devtools.py index 15b40e57..6630a472 100755 --- a/mmgen/devtools.py +++ b/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) diff --git a/mmgen/exception.py b/mmgen/exception.py index 916f08df..41d44302 100755 --- a/mmgen/exception.py +++ b/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 diff --git a/mmgen/filename.py b/mmgen/filename.py index 9da6b5a0..7e4f2415 100755 --- a/mmgen/filename.py +++ b/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): diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index a5bebb36..bf9fa98a 100755 --- a/mmgen/main_wallet.py +++ b/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] ' + 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()) +if cmd_args: + if invoked_as == 'gen' or len(cmd_args) > 1: + opts.usage() + check_infile(cmd_args[0]) + sf = get_seed_file(cmd_args,nargs,invoked_as=invoked_as) -if not invoked_as == 'chk': do_license_msg() +if invoked_as != 'chk': + do_license_msg() -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) - -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': diff --git a/mmgen/obj.py b/mmgen/obj.py index 28d266bf..0112938f 100755 --- a/mmgen/obj.py +++ b/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 diff --git a/mmgen/seed.py b/mmgen/seed.py index b9b77163..c4786609 100755 --- a/mmgen/seed.py +++ b/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): diff --git a/mmgen/util.py b/mmgen/util.py index e240fc0b..4fee4aff 100755 --- a/mmgen/util.py +++ b/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))) diff --git a/test/objtest.py b/test/objtest.py index 747c3f71..e9424a9a 100755 --- a/test/objtest.py +++ b/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 diff --git a/test/unit_tests_d/ut_subseed.py b/test/unit_tests_d/ut_subseed.py index e0aed492..a7b2ff0e 100755 --- a/test/unit_tests_d/ut_subseed.py +++ b/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)