Browse Source

move wallet classes from seed.py to new module wallet.py

The MMGen Project 5 years ago
parent
commit
ca1cdacaf1

+ 1 - 1
mmgen/common.py

@@ -29,7 +29,7 @@ from mmgen.util import *
 
 def help_notes(k):
 	from mmgen.obj import SubSeedIdxRange,SeedShareIdx,SeedShareCount,MasterShareIdx
-	from mmgen.seed import Wallet
+	from .wallet import Wallet
 	from mmgen.tx import MMGenTX
 	def fee_spec_letters(use_quotes=False):
 		cu = g.proto.coin_amt.units

+ 2 - 2
mmgen/filename.py

@@ -39,7 +39,7 @@ class Filename(MMGenObject):
 		self.ctime    = None
 		self.atime    = None
 
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		from mmgen.tx import MMGenTX
 		if ftype:
 			if isinstance(ftype,type):
@@ -99,7 +99,7 @@ def find_files_in_dir(ftype,fdir,no_dups=False):
 	if not isinstance(ftype,type):
 		die(3,"'{}': is of type {} (not a subclass of type 'type')".format(ftype,type(ftype)))
 
-	from mmgen.seed import Wallet
+	from .wallet import Wallet
 	if not issubclass(ftype,Wallet):
 		die(3,"'{}': not a recognized file type".format(ftype))
 

+ 1 - 1
mmgen/main_addrgen.py

@@ -24,7 +24,7 @@ mmgen-addrgen: Generate a series or range of addresses from an MMGen
 from mmgen.common import *
 from mmgen.crypto import *
 from mmgen.addr import *
-from mmgen.seed import Wallet
+from .wallet import Wallet
 
 if g.prog_name == 'mmgen-keygen':
 	gen_what = 'keys'

+ 2 - 2
mmgen/main_autosign.py

@@ -249,7 +249,7 @@ def decrypt_wallets():
 	opt.set_by_user = ['hash_preset']
 	opt.passwd_file = os.path.join(tx_dir,key_fn)
 #	opt.passwd_file = '/tmp/key'
-	from mmgen.seed import Wallet
+	from .wallet import Wallet
 	msg("Unlocking wallet{} with key from '{}'".format(suf(wfs),opt.passwd_file))
 	fails = 0
 	for wf in wfs:
@@ -352,7 +352,7 @@ def create_wallet_dir():
 def setup():
 	remove_wallet_dir()
 	gen_key(no_unmount=True)
-	from mmgen.seed import Wallet
+	from .wallet import Wallet
 	opt.hidden_incog_input_params = None
 	opt.quiet = True
 	opt.in_fmt = 'words'

+ 1 - 1
mmgen/main_passgen.py

@@ -24,7 +24,7 @@ mmgen-passgen: Generate a series or range of passwords from an MMGen
 from mmgen.common import *
 from mmgen.crypto import *
 from mmgen.addr import PasswordList,AddrIdxList
-from mmgen.seed import Wallet
+from .wallet import Wallet
 from mmgen.obj import MMGenPWIDString
 
 pwi = PasswordList.pw_info

+ 2 - 1
mmgen/main_seedjoin.py

@@ -23,7 +23,8 @@ mmgen/main_seedjoin: Regenerate an MMGen deterministic wallet from seed shares
 
 from mmgen.common import *
 from mmgen.obj import MasterShareIdx,SeedSplitIDString,MMGenWalletLabel
-from mmgen.seed import Seed,Wallet,SeedShareMasterJoining
+from .seed import Seed,SeedShareMasterJoining
+from .wallet import Wallet
 
 opts_data = {
 	'text': {

+ 1 - 1
mmgen/main_txbump.py

@@ -22,7 +22,7 @@ mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen
 """
 
 from mmgen.common import *
-from mmgen.seed import Wallet
+from .wallet import Wallet
 
 opts_data = {
 	'sets': [('yes', True, 'quiet', True)],

+ 1 - 1
mmgen/main_txdo.py

@@ -21,7 +21,7 @@ mmgen-txdo: Create, sign and broadcast an online MMGen transaction
 """
 
 from mmgen.common import *
-from mmgen.seed import Wallet
+from .wallet import Wallet
 from mmgen.obj import SubSeedIdxRange
 
 opts_data = {

+ 1 - 1
mmgen/main_txsign.py

@@ -22,7 +22,7 @@ mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
 
 from mmgen.common import *
 from mmgen.obj import SubSeedIdxRange
-from mmgen.seed import Wallet
+from .wallet import Wallet
 
 # -w, --use-wallet-dat (keys from running coin daemon) removed: use walletdump rpc instead
 opts_data = {

+ 1 - 1
mmgen/main_wallet.py

@@ -22,7 +22,7 @@ mmgen/main_wallet:  Entry point for MMGen wallet-related scripts
 
 import os
 from mmgen.common import *
-from mmgen.seed import Wallet,MMGenWallet
+from .wallet import Wallet,MMGenWallet
 from mmgen.filename import find_file_in_dir
 from mmgen.obj import MMGenWalletLabel,MasterShareIdx
 

+ 2 - 2
mmgen/opts.py

@@ -392,7 +392,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		msg_r('{} option {!r}{}'.format(beg,s,end))
 
 	def chk_in_fmt(key,val,desc):
-		from mmgen.seed import Wallet,IncogWallet,Brainwallet,IncogWalletHidden
+		from .wallet import Wallet,IncogWallet,Brainwallet,IncogWalletHidden
 		sstype = Wallet.fmt_code_to_type(val)
 		if not sstype:
 			opt_unrecognized(key,val)
@@ -434,7 +434,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 
 		if hasattr(opt,key2):
 			val2 = getattr(opt,key2)
-			from mmgen.seed import IncogWalletHidden
+			from .wallet import IncogWalletHidden
 			if val2 and val2 not in IncogWalletHidden.fmt_codes:
 				fs = 'Option conflict:\n  {}, with\n  {}={}'
 				raise UserOptError(fs.format(fmt_opt(key),fmt_opt(key2),val2))

+ 1 - 1144
mmgen/seed.py

@@ -20,34 +20,9 @@
 seed.py:  Seed-related classes and methods for the MMGen suite
 """
 
-import os
-
 from mmgen.common import *
 from mmgen.obj import *
-from mmgen.crypto import *
-from mmgen.baseconv import *
-
-pnm = g.proj_name
-
-def check_usr_seed_len(seed_len):
-	if opt.seed_len != seed_len and 'seed_len' in opt.set_by_user:
-		m = "ERROR: requested seed length ({}) doesn't match seed length of source ({})"
-		die(1,m.format((opt.seed_len,seed_len)))
-
-def _is_mnemonic(s,fmt):
-	oq_save = opt.quiet
-	opt.quiet = True
-	try:
-		Wallet(in_data=s,in_fmt=fmt)
-		ret = True
-	except:
-		ret = False
-	finally:
-		opt.quiet = oq_save
-	return ret
-
-def is_bip39_mnemonic(s): return _is_mnemonic(s,fmt='bip39')
-def is_mmgen_mnemonic(s): return _is_mnemonic(s,fmt='words')
+from .crypto import get_random,scramble_seed
 
 class SeedBase(MMGenObject):
 
@@ -498,1121 +473,3 @@ class SeedShareMasterJoining(SeedShareMaster):
 		self.id_str = id_str or 'default'
 		self.count = count
 		self.derived_seed = SeedBase(self.make_derived_seed_bin(self.id_str,self.count))
-
-class WalletMeta(type):
-	wallet_classes = set() # one-instance class, so store data in class attr
-	def __init__(cls,name,bases,namespace):
-		cls.wallet_classes.add(cls)
-		cls.wallet_classes -= set(bases)
-
-class Wallet(MMGenObject,metaclass=WalletMeta):
-
-	desc = g.proj_name + ' seed source'
-	file_mode = 'text'
-	stdin_ok = False
-	ask_tty = True
-	no_tty  = False
-	op = None
-	_msg = {}
-
-	class WalletData(MMGenObject): pass
-
-	def __new__(cls,fn=None,ss=None,seed_bin=None,seed=None,
-				passchg=False,in_data=None,ignore_in_fmt=False,in_fmt=None):
-
-		in_fmt = in_fmt or opt.in_fmt
-
-		if hasattr(opt,'out_fmt') and opt.out_fmt:
-			out_cls = cls.fmt_code_to_type(opt.out_fmt)
-			if not out_cls:
-				die(1,'{!r}: unrecognized output format'.format(opt.out_fmt))
-		else:
-			out_cls = None
-
-		def die_on_opt_mismatch(opt,sstype):
-			compare_or_die(
-				cls.fmt_code_to_type(opt).__name__, 'input format requested on command line',
-				sstype.__name__, 'input file format' )
-
-		if seed or seed_bin:
-			me = super(cls,cls).__new__(out_cls or MMGenWallet) # default to MMGenWallet
-			me.seed = seed or Seed(seed_bin=seed_bin)
-			me.op = 'new'
-		elif ss:
-			me = super(cls,cls).__new__((ss.__class__ if passchg else out_cls) or MMGenWallet)
-			me.seed = ss.seed
-			me.ss_in = ss
-			me.op = ('conv','pwchg_new')[bool(passchg)]
-		elif fn or opt.hidden_incog_input_params:
-			from mmgen.filename import Filename
-			if fn:
-				f = Filename(fn)
-			else:
-				# permit comma in filename
-				fn = ','.join(opt.hidden_incog_input_params.split(',')[:-1])
-				f = Filename(fn,ftype=IncogWalletHidden)
-			if in_fmt and not ignore_in_fmt:
-				die_on_opt_mismatch(in_fmt,f.ftype)
-			me = super(cls,cls).__new__(f.ftype)
-			me.infile = f
-			me.op = ('old','pwchg_old')[bool(passchg)]
-		elif in_fmt:
-			me = super(cls,cls).__new__(cls.fmt_code_to_type(in_fmt))
-			me.op = ('old','pwchg_old')[bool(passchg)]
-		else: # called with no arguments: initialize with random seed
-			me = super(cls,cls).__new__(out_cls or MMGenWallet)
-			me.seed = Seed(None)
-			me.op = 'new'
-
-		return me
-
-	def __init__(self,fn=None,ss=None,seed_bin=None,seed=None,
-				passchg=False,in_data=None,ignore_in_fmt=False,in_fmt=None):
-
-		self.ssdata = self.WalletData()
-		self.msg = {}
-		self.in_data = in_data
-
-		for c in reversed(self.__class__.__mro__):
-			if hasattr(c,'_msg'):
-				self.msg.update(c._msg)
-
-		if hasattr(self,'seed'):
-			self._encrypt()
-			return
-		elif hasattr(self,'infile') or self.in_data or not g.stdin_tty:
-			self._deformat_once()
-			self._decrypt_retry()
-		else:
-			if not self.stdin_ok:
-				die(1,'Reading from standard input not supported for {} format'.format(self.desc))
-			self._deformat_retry()
-			self._decrypt_retry()
-
-		m = ('',', seed length {}'.format(self.seed.bitlen))[self.seed.bitlen!=256]
-		qmsg('Valid {} for Seed ID {}{}'.format(self.desc,self.seed.sid.hl(),m))
-
-	def _get_data(self):
-		if hasattr(self,'infile'):
-			self.fmt_data = get_data_from_file(self.infile.name,self.desc,binary=self.file_mode=='binary')
-		elif self.in_data:
-			self.fmt_data = self.in_data
-		else:
-			self.fmt_data = self._get_data_from_user(self.desc)
-
-	def _get_data_from_user(self,desc):
-		return get_data_from_user(desc)
-
-	def _deformat_once(self):
-		self._get_data()
-		if not self._deformat():
-			die(2,'Invalid format for input data')
-
-	def _deformat_retry(self):
-		while True:
-			self._get_data()
-			if self._deformat():
-				break
-			msg('Trying again...')
-
-	def _decrypt_retry(self):
-		while True:
-			if self._decrypt():
-				break
-			if opt.passwd_file:
-				die(2,'Passphrase from password file, so exiting')
-			msg('Trying again...')
-
-	@classmethod
-	def get_extensions(cls):
-		return [c.ext for c in cls.wallet_classes if hasattr(c,'ext')]
-
-	@classmethod
-	def fmt_code_to_type(cls,fmt_code):
-		if fmt_code:
-			for c in cls.wallet_classes:
-				if fmt_code in getattr(c,'fmt_codes',[]):
-					return c
-		return None
-
-	@classmethod
-	def ext_to_type(cls,ext):
-		if ext:
-			for c in cls.wallet_classes:
-				if ext == getattr(c,'ext',None):
-					return c
-		return None
-
-	@classmethod
-	def format_fmt_codes(cls):
-		d = [(c.__name__,('.'+c.ext if c.ext else str(c.ext)),','.join(c.fmt_codes))
-					for c in cls.wallet_classes
-				if hasattr(c,'fmt_codes')]
-		w = max(len(i[0]) for i in d)
-		ret = ['{:<{w}}  {:<9} {}'.format(a,b,c,w=w) for a,b,c in [
-			('Format','FileExt','Valid codes'),
-			('------','-------','-----------')
-			] + sorted(d)]
-		return '\n'.join(ret) + ('','-α')[g.debug_utf8] + '\n'
-
-	def get_fmt_data(self):
-		self._format()
-		return self.fmt_data
-
-	def write_to_file(self,outdir='',desc=''):
-		self._format()
-		kwargs = {
-			'desc':     desc or self.desc,
-			'ask_tty':  self.ask_tty,
-			'no_tty':   self.no_tty,
-			'binary':   self.file_mode == 'binary'
-		}
-		# write_data_to_file(): outfile with absolute path overrides opt.outdir
-		if outdir:
-			of = os.path.abspath(os.path.join(outdir,self._filename()))
-		write_data_to_file(of if outdir else self._filename(),self.fmt_data,**kwargs)
-
-class WalletUnenc(Wallet):
-
-	def _decrypt_retry(self): pass
-	def _encrypt(self): pass
-
-	def _filename(self):
-		s = self.seed
-		return '{}[{}]{x}.{}'.format(
-			s.fn_stem,
-			s.bitlen,
-			self.ext,
-			x='-α' if g.debug_utf8 else '')
-
-	def _choose_seedlen(self,desc,ok_lens,subtype):
-
-		from mmgen.term import get_char
-		def choose_len():
-			prompt = self.choose_seedlen_prompt
-			while True:
-				r = get_char('\r'+prompt)
-				if is_int(r) and 1 <= int(r) <= len(ok_lens):
-					break
-			msg_r(('\r','\n')[g.test_suite] + ' '*len(prompt) + '\r')
-			return ok_lens[int(r)-1]
-
-		m1 = blue('{} type:'.format(capfirst(desc)))
-		m2 = yellow(subtype)
-		msg('{} {}'.format(m1,m2))
-
-		while True:
-			usr_len = choose_len()
-			prompt = self.choose_seedlen_confirm.format(usr_len)
-			if keypress_confirm(prompt,default_yes=True,no_nl=not g.test_suite):
-				return usr_len
-
-class WalletEnc(Wallet):
-
-	_msg = {
-		'choose_passphrase': """
-You must choose a passphrase to encrypt your new {} with.
-A key will be generated from your passphrase using a hash preset of '{}'.
-Please note that no strength checking of passphrases is performed.  For
-an empty passphrase, just hit ENTER twice.
-	""".strip()
-	}
-
-	def _get_hash_preset_from_user(self,hp,desc_suf=''):
-		n = ('','old ')[self.op=='pwchg_old']
-		m,n = (('to accept the default',n),('to reuse the old','new '))[self.op=='pwchg_new']
-		fs = "Enter {}hash preset for {}{}{},\n or hit ENTER {} value ('{}'): "
-		p = fs.format(
-			n,
-			('','new ')[self.op=='new'],
-			self.desc,
-			('',' '+desc_suf)[bool(desc_suf)],
-			m,
-			hp
-		)
-		while True:
-			ret = my_raw_input(p)
-			if ret:
-				if ret in g.hash_presets:
-					self.ssdata.hash_preset = ret
-					return ret
-				else:
-					msg('Invalid input.  Valid choices are {}'.format(', '.join(g.hash_presets)))
-			else:
-				self.ssdata.hash_preset = hp
-				return hp
-
-	def _get_hash_preset(self,desc_suf=''):
-		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'):
-			old_hp = self.ss_in.ssdata.hash_preset
-			if opt.keep_hash_preset:
-				qmsg("Reusing hash preset '{}' at user request".format(old_hp))
-				self.ssdata.hash_preset = old_hp
-			elif 'hash_preset' in opt.set_by_user:
-				hp = self.ssdata.hash_preset = opt.hash_preset
-				qmsg("Using hash preset '{}' requested on command line".format(opt.hash_preset))
-			else: # Prompt, using old value as default
-				hp = self._get_hash_preset_from_user(old_hp,desc_suf)
-
-			if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
-				m = ("changed to '{}'".format(hp),'unchanged')[hp==old_hp]
-				qmsg('Hash preset {}'.format(m))
-		elif 'hash_preset' in opt.set_by_user:
-			self.ssdata.hash_preset = opt.hash_preset
-			qmsg("Using hash preset '{}' requested on command line".format(opt.hash_preset))
-		else:
-			self._get_hash_preset_from_user(opt.hash_preset,desc_suf)
-
-	def _get_new_passphrase(self):
-		desc = '{}passphrase for {}{}'.format(
-				('','new ')[self.op=='pwchg_new'],
-				('','new ')[self.op in ('new','conv')],
-				self.desc
-			)
-		if opt.passwd_file:
-			w = pwfile_reuse_warning()
-			pw = ' '.join(get_words_from_file(opt.passwd_file,desc,quiet=w))
-		elif opt.echo_passphrase:
-			pw = ' '.join(get_words_from_user('Enter {}: '.format(desc)))
-		else:
-			for i in range(g.passwd_max_tries):
-				pw = ' '.join(get_words_from_user('Enter {}: '.format(desc)))
-				pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
-				dmsg('Passphrases: [{}] [{}]'.format(pw,pw2))
-				if pw == pw2:
-					vmsg('Passphrases match'); break
-				else: msg('Passphrases do not match.  Try again.')
-			else:
-				die(2,'User failed to duplicate passphrase in {} attempts'.format(g.passwd_max_tries))
-
-		if pw == '':
-			qmsg('WARNING: Empty passphrase')
-		self.ssdata.passwd = pw
-		return pw
-
-	def _get_passphrase(self,desc_suf=''):
-		desc = '{}passphrase for {}{}'.format(
-			('','old ')[self.op=='pwchg_old'],
-			self.desc,
-			('',' '+desc_suf)[bool(desc_suf)]
-		)
-		if opt.passwd_file:
-			w = pwfile_reuse_warning()
-			ret = ' '.join(get_words_from_file(opt.passwd_file,desc,quiet=w))
-		else:
-			ret = ' '.join(get_words_from_user('Enter {}: '.format(desc)))
-		self.ssdata.passwd = ret
-
-	def _get_first_pw_and_hp_and_encrypt_seed(self):
-		d = self.ssdata
-		self._get_hash_preset()
-
-		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'passwd'):
-			old_pw = self.ss_in.ssdata.passwd
-			if opt.keep_passphrase:
-				d.passwd = old_pw
-				qmsg('Reusing passphrase at user request')
-			else:
-				pw = self._get_new_passphrase()
-				if self.op == 'pwchg_new':
-					m = ('changed','unchanged')[pw==old_pw]
-					qmsg('Passphrase {}'.format(m))
-		else:
-			qmsg(self.msg['choose_passphrase'].format(self.desc,d.hash_preset))
-			self._get_new_passphrase()
-
-		d.salt     = sha256(get_random(128)).digest()[:g.salt_len]
-		key        = make_key(d.passwd, d.salt, d.hash_preset)
-		d.key_id   = make_chksum_8(key)
-		d.enc_seed = encrypt_seed(self.seed.data,key)
-
-class Mnemonic(WalletUnenc):
-
-	stdin_ok = True
-	wclass = 'mnemonic'
-	conv_cls = baseconv
-	choose_seedlen_prompt = 'Choose a mnemonic length: 1) 12 words, 2) 18 words, 3) 24 words: '
-	choose_seedlen_confirm = 'Mnemonic length of {} words chosen. OK?'
-
-	@property
-	def mn_lens(self):
-		return sorted(self.conv_cls.seedlen_map_rev[self.wl_id])
-
-	def _get_data_from_user(self,desc):
-
-		if not g.stdin_tty:
-			return get_data_from_user(desc)
-
-		from mmgen.mn_entry import mn_entry # import here to catch cfg var errors
-		mn_len = self._choose_seedlen(self.wclass,self.mn_lens,self.mn_type)
-		return mn_entry(self.wl_id).get_mnemonic_from_user(mn_len)
-
-	@staticmethod
-	def _mn2hex_pad(mn): return len(mn) * 8 // 3
-
-	@staticmethod
-	def _hex2mn_pad(hexnum): return len(hexnum) * 3 // 8
-
-	def _format(self):
-
-		hexseed = self.seed.hexdata
-
-		mn  = self.conv_cls.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
-		ret = self.conv_cls.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
-
-		# Internal error, so just die on fail
-		compare_or_die(ret,'recomputed seed',hexseed,'original',e='Internal error')
-
-		self.ssdata.mnemonic = mn
-		self.fmt_data = ' '.join(mn) + '\n'
-
-	def _deformat(self):
-
-		self.conv_cls.init_mn(self.wl_id)
-		mn = self.fmt_data.split()
-
-		if len(mn) not in self.mn_lens:
-			m = 'Invalid mnemonic ({} words).  Valid numbers of words: {}'
-			msg(m.format(len(mn),', '.join(map(str,self.mn_lens))))
-			return False
-
-		for n,w in enumerate(mn,1):
-			if w not in self.conv_cls.digits[self.wl_id]:
-				msg('Invalid mnemonic: word #{} is not in the {} wordlist'.format(n,self.wl_id.upper()))
-				return False
-
-		hexseed = self.conv_cls.tohex(mn,self.wl_id,self._mn2hex_pad(mn))
-		ret     = self.conv_cls.fromhex(hexseed,self.wl_id,self._hex2mn_pad(hexseed))
-
-		if len(hexseed) * 4 not in g.seed_lens:
-			msg('Invalid mnemonic (produces too large a number)')
-			return False
-
-		# Internal error, so just die
-		compare_or_die(' '.join(ret),'recomputed mnemonic',' '.join(mn),'original',e='Internal error')
-
-		self.seed = Seed(bytes.fromhex(hexseed))
-		self.ssdata.mnemonic = mn
-
-		check_usr_seed_len(self.seed.bitlen)
-
-		return True
-
-class MMGenMnemonic(Mnemonic):
-
-	fmt_codes = ('mmwords','words','mnemonic','mnem','mn','m')
-	desc = 'MMGen native mnemonic data'
-	mn_type = 'MMGen native'
-	ext = 'mmwords'
-	wl_id = 'mmgen'
-
-class BIP39Mnemonic(Mnemonic):
-
-	fmt_codes = ('bip39',)
-	desc = 'BIP39 mnemonic data'
-	mn_type = 'BIP39'
-	ext = 'bip39'
-	wl_id = 'bip39'
-
-	def __init__(self,*args,**kwargs):
-		from mmgen.bip39 import bip39
-		self.conv_cls = bip39
-		super().__init__(*args,**kwargs)
-
-class MMGenSeedFile(WalletUnenc):
-
-	stdin_ok = True
-	fmt_codes = ('mmseed','seed','s')
-	desc = 'seed data'
-	ext = 'mmseed'
-
-	def _format(self):
-		b58seed = baseconv.frombytes(self.seed.data,'b58',pad='seed',tostr=True)
-		self.ssdata.chksum = make_chksum_6(b58seed)
-		self.ssdata.b58seed = b58seed
-		self.fmt_data = '{} {}\n'.format(self.ssdata.chksum,split_into_cols(4,b58seed))
-
-	def _deformat(self):
-		desc = self.desc
-		ld = self.fmt_data.split()
-
-		if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11
-			msg('Invalid data length ({}) in {}'.format(len(ld),desc))
-			return False
-
-		a,b = ld[0],''.join(ld[1:])
-
-		if not is_chksum_6(a):
-			msg("'{}': invalid checksum format in {}".format(a, desc))
-			return False
-
-		if not is_b58_str(b):
-			msg("'{}': not a base 58 string, in {}".format(b, desc))
-			return False
-
-		vmsg_r('Validating {} checksum...'.format(desc))
-
-		if not compare_chksums(a,'file',make_chksum_6(b),'computed',verbose=True):
-			return False
-
-		ret = baseconv.tobytes(b,'b58',pad='seed')
-
-		if ret == False:
-			msg('Invalid base-58 encoded seed: {}'.format(val))
-			return False
-
-		self.seed = Seed(ret)
-		self.ssdata.chksum = a
-		self.ssdata.b58seed = b
-
-		check_usr_seed_len(self.seed.bitlen)
-
-		return True
-
-class DieRollSeedFile(WalletUnenc):
-
-	stdin_ok = True
-	fmt_codes = ('b6d','die','dieroll')
-	desc = 'base6d die roll seed data'
-	ext = 'b6d'
-	conv_cls = baseconv
-	wclass = 'dieroll'
-	wl_id = 'b6d'
-	mn_type = 'base6d'
-	choose_seedlen_prompt = 'Choose a seed length: 1) 128 bits, 2) 192 bits, 3) 256 bits: '
-	choose_seedlen_confirm = 'Seed length of {} bits chosen. OK?'
-	user_entropy_prompt = 'Would you like to provide some additional entropy from the keyboard?'
-	interactive_input = False
-
-	def _format(self):
-		d = baseconv.frombytes(self.seed.data,'b6d',pad='seed',tostr=True) + '\n'
-		self.fmt_data = block_format(d,gw=5,cols=5)
-
-	def _deformat(self):
-
-		d = remove_whitespace(self.fmt_data)
-
-		rmap = self.conv_cls.seedlen_map_rev['b6d']
-		if not len(d) in rmap:
-			m = '{!r}: invalid length for {} (must be one of {})'
-			raise SeedLengthError(m.format(len(d),self.desc,list(rmap)))
-
-		# truncate seed to correct length, discarding high bits
-		seed_len = rmap[len(d)]
-		seed_bytes = baseconv.tobytes(d,'b6d',pad='seed')[-seed_len:]
-
-		if self.interactive_input and opt.usr_randchars:
-			if keypress_confirm(self.user_entropy_prompt):
-				seed_bytes = add_user_random(seed_bytes,'die roll data')
-				self.desc += ' plus user-supplied entropy'
-
-		self.seed = Seed(seed_bytes)
-		self.ssdata.hexseed = seed_bytes.hex()
-
-		check_usr_seed_len(self.seed.bitlen)
-		return True
-
-	def _get_data_from_user(self,desc):
-
-		if not g.stdin_tty:
-			return get_data_from_user(desc)
-
-		seed_bitlens = [n*8 for n in sorted(self.conv_cls.seedlen_map['b6d'])]
-		seed_bitlen = self._choose_seedlen(self.wclass,seed_bitlens,self.mn_type)
-		nDierolls = self.conv_cls.seedlen_map['b6d'][seed_bitlen // 8]
-
-		m = """
-			For a {sb}-bit seed you must roll the die {nd} times.  After each die roll,
-			enter the result on the keyboard as a digit.  If you make an invalid entry,
-			you'll be prompted to re-enter it.
-		"""
-		msg('\n'+fmt(m.strip()).format(sb=seed_bitlen,nd=nDierolls)+'\n')
-
-		b6d_digits = self.conv_cls.digits['b6d']
-
-		cr = '\n' if g.test_suite else '\r'
-		prompt_fs = '\b\b\b   {}Enter die roll #{{}}: {}'.format(cr,CUR_SHOW)
-		clear_line = '' if g.test_suite else '\r' + ' ' * 25
-		invalid_msg = CUR_HIDE + cr + 'Invalid entry' + ' ' * 11
-
-		from mmgen.term import get_char
-		def get_digit(n):
-			p = prompt_fs
-			sleep = g.short_disp_timeout
-			while True:
-				ch = get_char(p.format(n),num_chars=1,sleep=sleep)
-				if ch in b6d_digits:
-					msg_r(CUR_HIDE + ' OK')
-					return ch
-				else:
-					msg_r(invalid_msg)
-					sleep = g.err_disp_timeout
-					p = clear_line + prompt_fs
-
-		dierolls,n = [],1
-		while len(dierolls) < nDierolls:
-			dierolls.append(get_digit(n))
-			n += 1
-
-		msg('Die rolls successfully entered' + CUR_SHOW)
-		self.interactive_input = True
-
-		return ''.join(dierolls)
-
-class PlainHexSeedFile(WalletUnenc):
-
-	stdin_ok = True
-	fmt_codes = ('hex','rawhex','plainhex')
-	desc = 'plain hexadecimal seed data'
-	ext = 'hex'
-
-	def _format(self):
-		self.fmt_data = self.seed.hexdata + '\n'
-
-	def _deformat(self):
-		desc = self.desc
-		d = self.fmt_data.strip()
-
-		if not is_hex_str_lc(d):
-			msg("'{}': not a lowercase hexadecimal string, in {}".format(d,desc))
-			return False
-
-		if not len(d)*4 in g.seed_lens:
-			msg('Invalid data length ({}) in {}'.format(len(d),desc))
-			return False
-
-		self.seed = Seed(bytes.fromhex(d))
-		self.ssdata.hexseed = d
-
-		check_usr_seed_len(self.seed.bitlen)
-
-		return True
-
-class MMGenHexSeedFile(WalletUnenc):
-
-	stdin_ok = True
-	fmt_codes = ('seedhex','hexseed','mmhex')
-	desc = 'hexadecimal seed data with checksum'
-	ext = 'mmhex'
-
-	def _format(self):
-		h = self.seed.hexdata
-		self.ssdata.chksum = make_chksum_6(h)
-		self.ssdata.hexseed = h
-		self.fmt_data = '{} {}\n'.format(self.ssdata.chksum, split_into_cols(4,h))
-
-	def _deformat(self):
-		desc = self.desc
-		d = self.fmt_data.split()
-		try:
-			d[1]
-			chk,hstr = d[0],''.join(d[1:])
-		except:
-			msg("'{}': invalid {}".format(self.fmt_data.strip(),desc))
-			return False
-
-		if not len(hstr)*4 in g.seed_lens:
-			msg('Invalid data length ({}) in {}'.format(len(hstr),desc))
-			return False
-
-		if not is_chksum_6(chk):
-			msg("'{}': invalid checksum format in {}".format(chk, desc))
-			return False
-
-		if not is_hex_str(hstr):
-			msg("'{}': not a hexadecimal string, in {}".format(hstr, desc))
-			return False
-
-		vmsg_r('Validating {} checksum...'.format(desc))
-
-		if not compare_chksums(chk,'file',make_chksum_6(hstr),'computed',verbose=True):
-			return False
-
-		self.seed = Seed(bytes.fromhex(hstr))
-		self.ssdata.chksum = chk
-		self.ssdata.hexseed = hstr
-
-		check_usr_seed_len(self.seed.bitlen)
-
-		return True
-
-class MMGenWallet(WalletEnc):
-
-	fmt_codes = ('wallet','w')
-	desc = g.proj_name + ' wallet'
-	ext = 'mmdat'
-
-	def _get_label_from_user(self,old_lbl=''):
-		d = "to reuse the label '{}'".format(old_lbl.hl()) if old_lbl else 'for no label'
-		p = 'Enter a wallet label, or hit ENTER {}: '.format(d)
-		while True:
-			msg_r(p)
-			ret = my_raw_input('')
-			if ret:
-				self.ssdata.label = MMGenWalletLabel(ret,on_fail='return')
-				if self.ssdata.label:
-					break
-				else:
-					msg('Invalid label.  Trying again...')
-			else:
-				self.ssdata.label = old_lbl or MMGenWalletLabel('No Label')
-				break
-		return self.ssdata.label
-
-	# nearly identical to _get_hash_preset() - factor?
-	def _get_label(self):
-		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'label'):
-			old_lbl = self.ss_in.ssdata.label
-			if opt.keep_label:
-				qmsg("Reusing label '{}' at user request".format(old_lbl.hl()))
-				self.ssdata.label = old_lbl
-			elif opt.label:
-				qmsg("Using label '{}' requested on command line".format(opt.label.hl()))
-				lbl = self.ssdata.label = opt.label
-			else: # Prompt, using old value as default
-				lbl = self._get_label_from_user(old_lbl)
-
-			if (not opt.keep_label) and self.op == 'pwchg_new':
-				m = ("changed to '{}'".format(lbl),'unchanged')[lbl==old_lbl]
-				qmsg('Label {}'.format(m))
-		elif opt.label:
-			qmsg("Using label '{}' requested on command line".format(opt.label.hl()))
-			self.ssdata.label = opt.label
-		else:
-			self._get_label_from_user()
-
-	def _encrypt(self):
-		self._get_first_pw_and_hp_and_encrypt_seed()
-		self._get_label()
-		d = self.ssdata
-		d.pw_status = ('NE','E')[len(d.passwd)==0]
-		d.timestamp = make_timestamp()
-
-	def _format(self):
-		d = self.ssdata
-		s = self.seed
-		slt_fmt  = baseconv.frombytes(d.salt,'b58',pad='seed',tostr=True)
-		es_fmt = baseconv.frombytes(d.enc_seed,'b58',pad='seed',tostr=True)
-		lines = (
-			d.label,
-			'{} {} {} {} {}'.format(s.sid.lower(), d.key_id.lower(),
-										s.bitlen, d.pw_status, d.timestamp),
-			'{}: {} {} {}'.format(d.hash_preset,*get_hash_params(d.hash_preset)),
-			'{} {}'.format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)),
-			'{} {}'.format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt))
-		)
-		chksum = make_chksum_6(' '.join(lines).encode())
-		self.fmt_data = '\n'.join((chksum,)+lines) + '\n'
-
-	def _deformat(self):
-
-		def check_master_chksum(lines,desc):
-
-			if len(lines) != 6:
-				msg('Invalid number of lines ({}) in {} data'.format(len(lines),desc))
-				return False
-
-			if not is_chksum_6(lines[0]):
-				msg('Incorrect master checksum ({}) in {} data'.format(lines[0],desc))
-				return False
-
-			chk = make_chksum_6(' '.join(lines[1:]))
-			if not compare_chksums(lines[0],'master',chk,'computed',
-						hdr='For wallet master checksum',verbose=True):
-				return False
-
-			return True
-
-		lines = self.fmt_data.splitlines()
-		if not check_master_chksum(lines,self.desc):
-			return False
-
-		d = self.ssdata
-		d.label = MMGenWalletLabel(lines[1])
-
-		d1,d2,d3,d4,d5 = lines[2].split()
-		d.seed_id = d1.upper()
-		d.key_id  = d2.upper()
-		check_usr_seed_len(int(d3))
-		d.pw_status,d.timestamp = d4,d5
-
-		hpdata = lines[3].split()
-
-		d.hash_preset = hp = hpdata[0][:-1]  # a string!
-		qmsg("Hash preset of wallet: '{}'".format(hp))
-		if 'hash_preset' in opt.set_by_user:
-			uhp = opt.hash_preset
-			if uhp != hp:
-				qmsg("Warning: ignoring user-requested hash preset '{}'".format(uhp))
-
-		hash_params = list(map(int,hpdata[1:]))
-
-		if hash_params != get_hash_params(d.hash_preset):
-			msg("Hash parameters '{}' don't match hash preset '{}'".format(' '.join(hash_params),d.hash_preset))
-			return False
-
-		lmin,foo,lmax = sorted(baseconv.seedlen_map_rev['b58']) # 22,33,44
-		for i,key in (4,'salt'),(5,'enc_seed'):
-			l = lines[i].split(' ')
-			chk = l.pop(0)
-			b58_val = ''.join(l)
-
-			if len(b58_val) < lmin or len(b58_val) > lmax:
-				msg('Invalid format for {} in {}: {}'.format(key,self.desc,l))
-				return False
-
-			if not compare_chksums(chk,key,
-					make_chksum_6(b58_val),'computed checksum',verbose=True):
-				return False
-
-			val = baseconv.tobytes(b58_val,'b58',pad='seed')
-			if val == False:
-				msg('Invalid base 58 number: {}'.format(b58_val))
-				return False
-
-			setattr(d,key,val)
-
-		return True
-
-	def _decrypt(self):
-		d = self.ssdata
-		# Needed for multiple transactions with {}-txsign
-		suf = ('',os.path.basename(self.infile.name))[bool(opt.quiet)]
-		self._get_passphrase(desc_suf=suf)
-		key = make_key(d.passwd, d.salt, d.hash_preset)
-		ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id)
-		if ret:
-			self.seed = Seed(ret)
-			return True
-		else:
-			return False
-
-	def _filename(self):
-		s = self.seed
-		d = self.ssdata
-		return '{}-{}[{},{}]{x}.{}'.format(
-				s.fn_stem,
-				d.key_id,
-				s.bitlen,
-				d.hash_preset,
-				self.ext,
-				x='-α' if g.debug_utf8 else '')
-
-class Brainwallet(WalletEnc):
-
-	stdin_ok = True
-	fmt_codes = ('mmbrain','brainwallet','brain','bw','b')
-	desc = 'brainwallet'
-	ext = 'mmbrain'
-	# brainwallet warning message? TODO
-
-	def get_bw_params(self):
-		# already checked
-		a = opt.brain_params.split(',')
-		return int(a[0]),a[1]
-
-	def _deformat(self):
-		self.brainpasswd = ' '.join(self.fmt_data.split())
-		return True
-
-	def _decrypt(self):
-		d = self.ssdata
-		# Don't set opt.seed_len! In txsign, BW seed len might differ from other seed srcs
-		if opt.brain_params:
-			seed_len,d.hash_preset = self.get_bw_params()
-		else:
-			if 'seed_len' not in opt.set_by_user:
-				m1 = 'Using default seed length of {} bits\n'
-				m2 = 'If this is not what you want, use the --seed-len option'
-				qmsg((m1+m2).format(yellow(str(opt.seed_len))))
-			self._get_hash_preset()
-			seed_len = opt.seed_len
-		qmsg_r('Hashing brainwallet data.  Please wait...')
-		# Use buflen arg of scrypt.hash() to get seed of desired length
-		seed = scrypt_hash_passphrase(self.brainpasswd.encode(),b'',d.hash_preset,buflen=seed_len//8)
-		qmsg('Done')
-		self.seed = Seed(seed)
-		msg('Seed ID: {}'.format(self.seed.sid))
-		qmsg('Check this value against your records')
-		return True
-
-	def _format(self):
-		raise NotImplementedError('Brainwallet not supported as an output format')
-
-	def _encrypt(self):
-		raise NotImplementedError('Brainwallet not supported as an output format')
-
-class IncogWalletBase(WalletEnc):
-
-	_msg = {
-		'check_incog_id': """
-  Check the generated Incog ID above against your records.  If it doesn't
-  match, then your incognito data is incorrect or corrupted.
-	""",
-		'record_incog_id': """
-  Make a record of the Incog ID but keep it secret.  You will use it to
-  identify your incog wallet data in the future.
-	""",
-		'incorrect_incog_passphrase_try_again': """
-Incorrect passphrase, hash preset, or maybe old-format incog wallet.
-Try again? (Y)es, (n)o, (m)ore information:
-""".strip(),
-		'confirm_seed_id': """
-If the Seed ID above is correct but you're seeing this message, then you need
-to exit and re-run the program with the '--old-incog-fmt' option.
-""".strip(),
-		'dec_chk': " {} hash preset"
-	}
-
-	def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper()
-
-	def _get_incog_data_len(self,seed_len):
-		e = (g.hincog_chk_len,0)[bool(opt.old_incog_fmt)]
-		return g.aesctr_iv_len + g.salt_len + e + seed_len//8
-
-	def _incog_data_size_chk(self):
-		# valid sizes: 56, 64, 72
-		dlen = len(self.fmt_data)
-		valid_dlen = self._get_incog_data_len(opt.seed_len)
-		if dlen == valid_dlen:
-			return True
-		else:
-			if opt.old_incog_fmt:
-				msg('WARNING: old-style incognito format requested.  Are you sure this is correct?')
-			m = 'Invalid incognito data size ({} bytes) for this seed length ({} bits)'
-			msg(m.format(dlen,opt.seed_len))
-			msg('Valid data size for this seed length: {} bytes'.format(valid_dlen))
-			for sl in g.seed_lens:
-				if dlen == self._get_incog_data_len(sl):
-					die(1,'Valid seed length for this data size: {} bits'.format(sl))
-			msg('This data size ({} bytes) is invalid for all available seed lengths'.format(dlen))
-			return False
-
-	def _encrypt (self):
-		self._get_first_pw_and_hp_and_encrypt_seed()
-		if opt.old_incog_fmt:
-			die(1,'Writing old-format incog wallets is unsupported')
-		d = self.ssdata
-		# IV is used BOTH to initialize counter and to salt password!
-		d.iv = get_random(g.aesctr_iv_len)
-		d.iv_id = self._make_iv_chksum(d.iv)
-		msg('New Incog Wallet ID: {}'.format(d.iv_id))
-		qmsg('Make a record of this value')
-		vmsg(self.msg['record_incog_id'])
-
-		d.salt = get_random(g.salt_len)
-		key = make_key(d.passwd, d.salt, d.hash_preset, 'incog wallet key')
-		chk = sha256(self.seed.data).digest()[:8]
-		d.enc_seed = encrypt_data(chk+self.seed.data, key, g.aesctr_dfl_iv, 'seed')
-
-		d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, 'incog wrapper key')
-		d.key_id = make_chksum_8(d.wrapper_key)
-		vmsg('Key ID: {}'.format(d.key_id))
-		d.target_data_len = self._get_incog_data_len(self.seed.bitlen)
-
-	def _format(self):
-		d = self.ssdata
-		self.fmt_data = d.iv + encrypt_data(d.salt+d.enc_seed, d.wrapper_key, d.iv, self.desc)
-
-	def _filename(self):
-		s = self.seed
-		d = self.ssdata
-		return '{}-{}-{}[{},{}]{x}.{}'.format(
-				s.fn_stem,
-				d.key_id,
-				d.iv_id,
-				s.bitlen,
-				d.hash_preset,
-				self.ext,
-				x='-α' if g.debug_utf8 else '')
-
-	def _deformat(self):
-
-		if not self._incog_data_size_chk():
-			return False
-
-		d = self.ssdata
-		d.iv             = self.fmt_data[0:g.aesctr_iv_len]
-		d.incog_id       = self._make_iv_chksum(d.iv)
-		d.enc_incog_data = self.fmt_data[g.aesctr_iv_len:]
-		msg('Incog Wallet ID: {}'.format(d.incog_id))
-		qmsg('Check this value against your records')
-		vmsg(self.msg['check_incog_id'])
-
-		return True
-
-	def _verify_seed_newfmt(self,data):
-		chk,seed = data[:8],data[8:]
-		if sha256(seed).digest()[:8] == chk:
-			qmsg('Passphrase{} are correct'.format(self.msg['dec_chk'].format('and')))
-			return seed
-		else:
-			msg('Incorrect passphrase{}'.format(self.msg['dec_chk'].format('or')))
-			return False
-
-	def _verify_seed_oldfmt(self,seed):
-		m = 'Seed ID: {}.  Is the Seed ID correct?'.format(make_chksum_8(seed))
-		if keypress_confirm(m, True):
-			return seed
-		else:
-			return False
-
-	def _decrypt(self):
-		d = self.ssdata
-		self._get_hash_preset(desc_suf=d.incog_id)
-		self._get_passphrase(desc_suf=d.incog_id)
-
-		# IV is used BOTH to initialize counter and to salt password!
-		key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key')
-		dd = decrypt_data(d.enc_incog_data, key, d.iv, 'incog data')
-
-		d.salt     = dd[0:g.salt_len]
-		d.enc_seed = dd[g.salt_len:]
-
-		key = make_key(d.passwd, d.salt, d.hash_preset, 'main key')
-		qmsg('Key ID: {}'.format(make_chksum_8(key)))
-
-		verify_seed = getattr(self,'_verify_seed_'+
-						('newfmt','oldfmt')[bool(opt.old_incog_fmt)])
-
-		seed = verify_seed(decrypt_seed(d.enc_seed, key, '', ''))
-
-		if seed:
-			self.seed = Seed(seed)
-			msg('Seed ID: {}'.format(self.seed.sid))
-			return True
-		else:
-			return False
-
-class IncogWallet(IncogWalletBase):
-
-	desc = 'incognito data'
-	fmt_codes = ('mmincog','incog','icg','i')
-	ext = 'mmincog'
-	file_mode = 'binary'
-	no_tty = True
-
-class IncogWalletHex(IncogWalletBase):
-
-	desc = 'hex incognito data'
-	fmt_codes = ('mmincox','incox','incog_hex','xincog','ix','xi')
-	ext = 'mmincox'
-	file_mode = 'text'
-	no_tty = False
-
-	def _deformat(self):
-		ret = decode_pretty_hexdump(self.fmt_data)
-		if ret:
-			self.fmt_data = ret
-			return super()._deformat()
-		else:
-			return False
-
-	def _format(self):
-		super()._format()
-		self.fmt_data = pretty_hexdump(self.fmt_data)
-
-class IncogWalletHidden(IncogWalletBase):
-
-	desc = 'hidden incognito data'
-	fmt_codes = ('incog_hidden','hincog','ih','hi')
-	ext = None
-	file_mode = 'binary'
-	no_tty = True
-
-	_msg = {
-		'choose_file_size': """
-You must choose a size for your new hidden incog data.  The minimum size is
-{} bytes, which puts the incog data right at the end of the file. Since you
-probably want to hide your data somewhere in the middle of the file where it's
-harder to find, you're advised to choose a much larger file size than this.
-	""".strip(),
-		'check_incog_id': """
-  Check generated Incog ID above against your records.  If it doesn't
-  match, then your incognito data is incorrect or corrupted, or you
-  may have specified an incorrect offset.
-	""",
-		'record_incog_id': """
-  Make a record of the Incog ID but keep it secret.  You will used it to
-  identify the incog wallet data in the future and to locate the offset
-  where the data is hidden in the event you forget it.
-	""",
-		'dec_chk': ', hash preset, offset {} seed length'
-	}
-
-	def _get_hincog_params(self,wtype):
-		a = getattr(opt,'hidden_incog_'+ wtype +'_params').split(',')
-		return ','.join(a[:-1]),int(a[-1]) # permit comma in filename
-
-	def _check_valid_offset(self,fn,action):
-		d = self.ssdata
-		m = ('Input','Destination')[action=='write']
-		if fn.size < d.hincog_offset + d.target_data_len:
-			fs = "{} file '{}' has length {}, too short to {} {} bytes of data at offset {}"
-			die(1,fs.format(m,fn.name,fn.size,action,d.target_data_len,d.hincog_offset))
-
-	def _get_data(self):
-		d = self.ssdata
-		d.hincog_offset = self._get_hincog_params('input')[1]
-
-		qmsg("Getting hidden incog data from file '{}'".format(self.infile.name))
-
-		# Already sanity-checked:
-		d.target_data_len = self._get_incog_data_len(opt.seed_len)
-		self._check_valid_offset(self.infile,'read')
-
-		flgs = os.O_RDONLY|os.O_BINARY if g.platform == 'win' else os.O_RDONLY
-		fh = os.open(self.infile.name,flgs)
-		os.lseek(fh,int(d.hincog_offset),os.SEEK_SET)
-		self.fmt_data = os.read(fh,d.target_data_len)
-		os.close(fh)
-		qmsg("Data read from file '{}' at offset {}".format(self.infile.name,d.hincog_offset))
-
-	# overrides method in Wallet
-	def write_to_file(self):
-		d = self.ssdata
-		self._format()
-		compare_or_die(d.target_data_len, 'target data length',
-				len(self.fmt_data),'length of formatted ' + self.desc)
-
-		k = ('output','input')[self.op=='pwchg_new']
-		fn,d.hincog_offset = self._get_hincog_params(k)
-
-		if opt.outdir and not os.path.dirname(fn):
-			fn = os.path.join(opt.outdir,fn)
-
-		check_offset = True
-		try:
-			os.stat(fn)
-		except:
-			if keypress_confirm("Requested file '{}' does not exist.  Create?".format(fn),default_yes=True):
-				min_fsize = d.target_data_len + d.hincog_offset
-				msg(self.msg['choose_file_size'].format(min_fsize))
-				while True:
-					fsize = parse_bytespec(my_raw_input('Enter file size: '))
-					if fsize >= min_fsize:
-						break
-					msg('File size must be an integer no less than {}'.format(min_fsize))
-
-				from mmgen.tool import MMGenToolCmdFileUtil
-				MMGenToolCmdFileUtil().rand2file(fn,str(fsize))
-				check_offset = False
-			else:
-				die(1,'Exiting at user request')
-
-		from mmgen.filename import Filename
-		f = Filename(fn,ftype=type(self),write=True)
-
-		dmsg('{} data len {}, offset {}'.format(capfirst(self.desc),d.target_data_len,d.hincog_offset))
-
-		if check_offset:
-			self._check_valid_offset(f,'write')
-			if not opt.quiet:
-				confirm_or_raise('',"alter file '{}'".format(f.name))
-
-		flgs = os.O_RDWR|os.O_BINARY if g.platform == 'win' else os.O_RDWR
-		fh = os.open(f.name,flgs)
-		os.lseek(fh, int(d.hincog_offset), os.SEEK_SET)
-		os.write(fh, self.fmt_data)
-		os.close(fh)
-		msg("{} written to file '{}' at offset {}".format(capfirst(self.desc),f.name,d.hincog_offset))

+ 5 - 5
mmgen/tool.py

@@ -786,14 +786,14 @@ class MMGenToolCmdWallet(MMGenToolCmds):
 		"get the Seed ID of a single subseed by Subseed Index for default or specified wallet"
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		return Wallet(sf).seed.subseed(subseed_idx).sid
 
 	def get_subseed_by_seed_id(self,seed_id:str,wallet='',last_idx=g.subseeds):
 		"get the Subseed Index of a single subseed by Seed ID for default or specified wallet"
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		ret = Wallet(sf).seed.subseed_by_seed_id(seed_id,last_idx)
 		return ret.ss_idx if ret else None
 
@@ -801,7 +801,7 @@ class MMGenToolCmdWallet(MMGenToolCmds):
 		"list a range of subseed Seed IDs for default or specified wallet"
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		return Wallet(sf).seed.subseeds.format(*SubSeedIdxRange(subseed_idx_range))
 
 	def list_shares(self,
@@ -812,7 +812,7 @@ class MMGenToolCmdWallet(MMGenToolCmds):
 		"list the Seed IDs of the shares resulting from a split of default or specified wallet"
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		return Wallet(sf).seed.split(share_count,id_str,master_share).format()
 
 	def gen_key(self,mmgen_addr:str,wallet=''):
@@ -824,7 +824,7 @@ class MMGenToolCmdWallet(MMGenToolCmds):
 		addr = MMGenID(mmgen_addr)
 		opt.quiet = True
 		sf = get_seed_file([wallet] if wallet else [],1)
-		from mmgen.seed import Wallet
+		from .wallet import Wallet
 		ss = Wallet(sf)
 		if ss.seed.sid != addr.sid:
 			m = 'Seed ID of requested address ({}) does not match wallet ({})'

+ 1 - 1
mmgen/txsign.py

@@ -21,7 +21,7 @@ txsign: Sign a transaction generated by 'mmgen-txcreate'
 """
 
 from mmgen.common import *
-from mmgen.seed import *
+from .wallet import *
 from mmgen.tx import *
 from mmgen.addr import *
 

+ 2 - 2
mmgen/util.py

@@ -470,7 +470,7 @@ def check_outfile(f,blkdev_ok=False):
 def check_outdir(f):
 	return check_file_type_and_access(f,'output directory')
 def check_wallet_extension(fn):
-	from mmgen.seed import Wallet
+	from .wallet import Wallet
 	if not Wallet.ext_to_type(get_extension(fn)):
 		raise BadFileExtension("'{}': unrecognized seed source file extension".format(fn))
 def make_full_path(outdir,outfile):
@@ -478,7 +478,7 @@ def make_full_path(outdir,outfile):
 
 def get_seed_file(cmd_args,nargs,invoked_as=None):
 	from mmgen.filename import find_file_in_dir
-	from mmgen.seed import MMGenWallet
+	from .wallet import MMGenWallet
 
 	wf = find_file_in_dir(MMGenWallet,g.data_dir)
 

+ 3 - 2
setup.py

@@ -106,6 +106,7 @@ setup(
 			'mmgen.color',
 			'mmgen.common',
 			'mmgen.crypto',
+			'mmgen.daemon',
 			'mmgen.devtools',
 			'mmgen.ed25519',
 			'mmgen.ed25519ll_djbec',
@@ -114,8 +115,8 @@ setup(
 			'mmgen.globalvars',
 			'mmgen.keccak',
 			'mmgen.license',
-			'mmgen.mn_entry',
 			'mmgen.mn_electrum',
+			'mmgen.mn_entry',
 			'mmgen.mn_monero',
 			'mmgen.mn_tirosh',
 			'mmgen.obj',
@@ -126,12 +127,12 @@ setup(
 			'mmgen.seed',
 			'mmgen.sha2',
 			'mmgen.term',
-			'mmgen.daemon',
 			'mmgen.tool',
 			'mmgen.tw',
 			'mmgen.tx',
 			'mmgen.txsign',
 			'mmgen.util',
+			'mmgen.wallet',
 
 			'mmgen.altcoins.__init__',
 

+ 1 - 1
test/test_py_d/ts_input.py

@@ -13,7 +13,7 @@ ts_input.py: user input tests for the MMGen test.py test suite
 from ..include.common import *
 from .ts_base import *
 from .input import *
-from mmgen.seed import Wallet
+from mmgen.wallet import Wallet
 
 class TestSuiteInput(TestSuiteBase):
 	'user input'

+ 1 - 1
test/test_py_d/ts_main.py

@@ -22,7 +22,7 @@ ts_main.py: Basic operations tests for the test.py test suite
 
 from mmgen.globalvars import g
 from mmgen.opts import opt
-from mmgen.seed import Wallet,MMGenWallet,MMGenMnemonic,IncogWallet,MMGenSeedFile
+from mmgen.wallet import Wallet,MMGenWallet,MMGenMnemonic,IncogWallet,MMGenSeedFile
 from ..include.common import *
 from .common import *
 from .ts_base import *

+ 1 - 1
test/test_py_d/ts_ref.py

@@ -23,7 +23,7 @@ ts_ref.py: Reference file tests for the test.py test suite
 import os
 from mmgen.globalvars import g
 from mmgen.opts import opt
-from mmgen.seed import MMGenMnemonic
+from mmgen.wallet import MMGenMnemonic
 from ..include.common import *
 from .common import *
 

+ 1 - 7
test/test_py_d/ts_ref_3seed.py

@@ -23,7 +23,7 @@ ts_ref_3seed.py: Saved and generated reference file tests for 128, 192 and
 
 from mmgen.globalvars import g
 from mmgen.opts import opt
-from mmgen.seed import Wallet
+from mmgen.wallet import *
 from ..include.common import *
 from .common import *
 from .ts_base import *
@@ -89,27 +89,21 @@ class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
 		return self.walletchk(wf,pf=None,wcls=ss,sid=self.seed_id)
 
 	def ref_seed_chk(self):
-		from mmgen.seed import MMGenSeedFile
 		return self.ref_ss_chk(ss=MMGenSeedFile)
 
 	def ref_hex_chk(self):
-		from mmgen.seed import MMGenHexSeedFile
 		return self.ref_ss_chk(ss=MMGenHexSeedFile)
 
 	def ref_plainhex_chk(self):
-		from mmgen.seed import PlainHexSeedFile
 		return self.ref_ss_chk(ss=PlainHexSeedFile)
 
 	def ref_dieroll_chk(self):
-		from mmgen.seed import DieRollSeedFile
 		return self.ref_ss_chk(ss=DieRollSeedFile)
 
 	def ref_mn_chk(self):
-		from mmgen.seed import MMGenMnemonic
 		return self.ref_ss_chk(ss=MMGenMnemonic)
 
 	def ref_bip39_chk(self):
-		from mmgen.seed import BIP39Mnemonic
 		return self.ref_ss_chk(ss=BIP39Mnemonic)
 
 	def ref_hincog_chk(self,desc='hidden incognito data'):

+ 1 - 1
test/test_py_d/ts_regtest.py

@@ -27,7 +27,7 @@ from mmgen.opts import opt
 from mmgen.util import die,gmsg,write_data_to_file
 from mmgen.protocol import CoinProtocol
 from mmgen.addr import AddrList
-from mmgen.seed import MMGenWallet
+from mmgen.wallet import MMGenWallet
 from ..include.common import *
 from .common import *
 

+ 1 - 1
test/test_py_d/ts_seedsplit.py

@@ -22,7 +22,7 @@ ts_seedsplit.py: Seed split/join tests for the test.py test suite
 
 from mmgen.globalvars import g
 from mmgen.opts import opt
-from mmgen.seed import Wallet,MMGenWallet,IncogWallet,IncogWalletHex,IncogWalletHidden,WalletEnc
+from mmgen.wallet import Wallet,MMGenWallet,IncogWallet,IncogWalletHex,IncogWalletHidden,WalletEnc
 
 from .ts_base import *
 

+ 1 - 1
test/test_py_d/ts_shared.py

@@ -24,7 +24,7 @@ import os
 from mmgen.globalvars import g
 from mmgen.opts import opt
 from mmgen.util import ymsg
-from mmgen.seed import Wallet,WalletEnc,Brainwallet,MMGenWallet,IncogWalletHidden
+from mmgen.wallet import Wallet,WalletEnc,Brainwallet,MMGenWallet,IncogWalletHidden
 from ..include.common import *
 from .common import *
 

+ 1 - 1
test/test_py_d/ts_wallet.py

@@ -22,7 +22,7 @@ ts_wallet.py: Wallet conversion tests for the test.py test suite
 
 import os
 from mmgen.opts import opt
-from mmgen.seed import *
+from mmgen.wallet import *
 from .common import *
 from .ts_base import *
 from .ts_shared import *

+ 1 - 1
test/tooltest2.py

@@ -31,7 +31,7 @@ from include.tests_header import repo_root
 from mmgen.common import *
 from test.include.common import *
 from mmgen.obj import is_wif,is_coin_addr
-from mmgen.seed import is_bip39_mnemonic,is_mmgen_mnemonic
+from mmgen.wallet import is_bip39_mnemonic,is_mmgen_mnemonic
 from mmgen.addr import is_xmrseed
 from mmgen.baseconv import *