Browse Source

Minor changes, bugfixes

philemon 9 years ago
parent
commit
8fc94a8c64
21 changed files with 662 additions and 754 deletions
  1. 0 1
      INSTALL
  2. 5 10
      mmgen/addr.py
  3. 11 2
      mmgen/crypto.py
  4. 1 1
      mmgen/main_addrgen.py
  5. 1 1
      mmgen/main_addrimport.py
  6. 2 2
      mmgen/main_pywallet.py
  7. 7 1
      mmgen/main_tool.py
  8. 11 13
      mmgen/main_txcreate.py
  9. 2 4
      mmgen/main_txsend.py
  10. 20 21
      mmgen/main_txsign.py
  11. 1 1
      mmgen/main_wallet.py
  12. 0 149
      mmgen/mnemonic.py
  13. 2 4
      mmgen/rpc/proxy.py
  14. 68 30
      mmgen/seed.py
  15. 3 3
      mmgen/share/Opts.py
  16. 12 8
      mmgen/test.py
  17. 53 45
      mmgen/tool.py
  18. 51 195
      mmgen/util.py
  19. 0 1
      setup.py
  20. 403 251
      test/test.py
  21. 9 11
      test/tooltest.py

+ 0 - 1
INSTALL

@@ -1,4 +1,3 @@
-
 	MMGen is written in Pure Python and runs on MS Windows and Linux.
 
 	Instructions for installation and use reside on MMGen's Github wiki:

+ 5 - 10
mmgen/addr.py

@@ -176,7 +176,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
 
 def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 
-	if buf: lines = remove_comments(buf.split("\n"))
+	if buf: lines = remove_comments(buf.splitlines()) # DOS-safe
 	else:   lines = get_lines_from_file(fn,"address data",trim_comments=True)
 
 	try:
@@ -190,7 +190,7 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 		elif cbrace != '}':
 			errmsg = "'%s': invalid last line" % cbrace
 		elif not is_mmgen_seed_id(sid):
-			errmsg = "'%s': invalid seed ID" % sid
+			errmsg = "'%s': invalid Seed ID" % sid
 		else:
 			ret = _parse_addrfile_body(lines[1:-1],has_keys)
 			if type(ret) == list: return sid,ret
@@ -203,14 +203,9 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 		return False
 
 
-def _parse_keyaddr_file(infile):
-	d = get_data_from_file(infile,"{pnm} key-address file data".format(pnm=pnm))
-	enc_ext = get_extension(infile) == g.mmenc_ext
-	if enc_ext or not is_utf8(d):
-		m = "Decrypting" if enc_ext else "Attempting to decrypt"
-		msg("%s key-address file %s" % (m,infile))
-		from crypto import mmgen_decrypt_retry
-		d = mmgen_decrypt_retry(d,"key-address file")
+def _parse_keyaddr_file(fn):
+	from mmgen.crypto import mmgen_decrypt_file_maybe
+	d = mmgen_decrypt_file_maybe(fn,"key-address file")
 	return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False)
 
 

+ 11 - 2
mmgen/crypto.py

@@ -52,7 +52,7 @@ keystrokes will also be used as a source of randomness.
 # 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
+# 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(),
 }
@@ -75,7 +75,7 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
 	chk2 = make_chksum_8(dec_seed)
 
 	if seed_id:
-		if compare_chksums(seed_id,"seed ID",chk2,"decrypted seed"):
+		if compare_chksums(seed_id,"Seed ID",chk2,"decrypted seed"):
 			qmsg("Passphrase is OK")
 		else:
 			if not opt.debug:
@@ -246,6 +246,15 @@ def mmgen_decrypt(data,desc="data",hash_preset=""):
 		msg("Incorrect passphrase or hash preset")
 		return False
 
+def mmgen_decrypt_file_maybe(fn,desc):
+	d = get_data_from_file(fn,"{} data".format(desc),binary=True)
+	have_enc_ext = get_extension(fn) == g.mmenc_ext
+	if have_enc_ext or not is_ascii(d):
+		m = ("Attempting to decrypt","Decrypting")[int(have_enc_ext)]
+		msg("%s %s %s" % (m,desc,fn))
+		d = mmgen_decrypt_retry(d,desc)
+	return d
+
 def mmgen_decrypt_retry(d,desc="data"):
 	while True:
 		d_dec = mmgen_decrypt(d,desc)

+ 1 - 1
mmgen/main_addrgen.py

@@ -92,7 +92,7 @@ FMT CODES:
   {f}
 """.format(
 		n=note1,
-		f="\n  ".join(SeedSource.format_fmt_codes().split("\n")),
+		f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
 		o=opt.opts
 	)
 }

+ 1 - 1
mmgen/main_addrimport.py

@@ -76,7 +76,7 @@ for e in ai.addrdata:
 		msg("%s: invalid address" % e.addr)
 		sys.exit(2)
 
-m = (" from seed ID %s" % ai.seed_id) if ai.seed_id else ""
+m = (" from Seed ID %s" % ai.seed_id) if ai.seed_id else ""
 qmsg("OK. %s addresses%s" % (ai.num_addrs,m))
 
 g.http_timeout = 3600

+ 2 - 2
mmgen/main_pywallet.py

@@ -61,7 +61,7 @@ import math
 
 import mmgen.globalvars as g
 import mmgen.opt as opt
-from mmgen.util import msg,mdie,mmsg,write_data_to_file
+from mmgen.util import msg
 
 max_version = 60000
 addrtype = 0
@@ -1664,7 +1664,7 @@ len_arg = "%s" % len(wallet_addrs) \
 	if len(data) == len(wallet_addrs) or ext == "json" \
 		else "%s:%s" % (len(data),len(wallet_addrs))
 
-from mmgen.util import make_chksum_8,write_to_file,write_to_stdout
+from mmgen.util import make_chksum_8,write_data_to_file
 wallet_id = make_chksum_8(str(sorted(wallet_addrs)))
 
 data = "\n".join(data) + "\n"

+ 7 - 1
mmgen/main_tool.py

@@ -32,6 +32,7 @@ opts_data = {
 	'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -h, --help            Print this help message
+-P, --passwd-file= f  Get passphrase from file 'f'.
 -q, --quiet           Produce quieter output
 -r, --usr-randchars=n Get 'n' characters of additional randomness from
                       user (min={g.min_urandchars}, max={g.max_urandchars})
@@ -45,7 +46,12 @@ command
 """.format(tool.cmd_help,g.prog_name)
 }
 
-cmd_args = opt.opts.init(opts_data,add_opts=["no_keyconv"])
+cmd_args = opt.opts.init(opts_data,
+	add_opts=[
+		"no_keyconv",
+		"hidden_incog_input_params",
+		"in_fmt"
+		])
 
 if len(cmd_args) < 1:
 	opt.opts.usage()

+ 11 - 13
mmgen/main_txcreate.py

@@ -155,7 +155,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 	from copy import deepcopy
 	from mmgen.term import get_terminal_size
 
-	write_to_file_msg = ""
+	written_to_file_msg = ""
 	msg("")
 
 	while True:
@@ -215,8 +215,8 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 		out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age)
 					for n,i in enumerate(unsp)]
 
-		msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg)
-		write_to_file_msg = ""
+		msg("\n".join(out) +"\n\n" + written_to_file_msg + options_msg)
+		written_to_file_msg = ""
 
 		skip_prompt = False
 
@@ -241,8 +241,8 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 			elif reply == 'p':
 				d = format_unspent_outputs_for_printing(unsp,sort_info,total)
 				of = "listunspent[%s].out" % ",".join(sort_info)
-				write_to_file(of, d, "",False,False)
-				write_to_file_msg = "Data written to '%s'\n\n" % of
+				write_data_to_file(of,d,"unspent outputs listing")
+				written_to_file_msg = "Data written to '%s'\n\n" % of
 			elif reply == 'v':
 				do_pager("\n".join(out))
 				continue
@@ -390,7 +390,8 @@ if g.bogus_wallet_data:  # for debugging purposes only
 	us = eval(get_data_from_file(g.bogus_wallet_data))
 else:
 	us = c.listunspent()
-#	write_to_file("bogus_unspent.json", repr(us)); sys.exit()
+#	write_data_to_file("bogus_unspent.json", repr(us), "bogus unspent data")
+#	sys.exit()
 
 if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2)
 for o in us:
@@ -470,10 +471,7 @@ b2m_map = make_b2m_map(sel_unspent,tx_out,ail_w,ail_f)
 prompt_and_view_tx_data(c,"View decoded transaction?",
 		sel_unspent,tx_hex,b2m_map,comment,metadata)
 
-if keypress_confirm("Save transaction?",default_yes=False):
-	outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
-	data = make_tx_data("{} {} {}".format(*metadata),
-				tx_hex,sel_unspent,b2m_map,comment)
-	write_to_file(outfile,data,"transaction",False,True)
-else:
-	msg("Transaction not saved")
+outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
+data = make_tx_data("{} {} {}".format(*metadata),
+			tx_hex,sel_unspent,b2m_map,comment)
+write_data_to_file(outfile,data,"transaction",ask_write_default_yes=False)

+ 2 - 4
mmgen/main_txsend.py

@@ -63,9 +63,7 @@ if keypress_confirm("Edit transaction comment?"):
 	comment = get_tx_comment_from_user(comment)
 	data = make_tx_data("{} {} {}".format(*metadata), tx_hex,
 				inputs_data, b2m_map, comment)
-	w = "signed transaction with edited comment"
-	outfile = infile
-	write_to_file(outfile,data,w,False,True,True)
+	write_data_to_file(infile,data,"signed transaction with edited comment")
 
 warn   = "Once this transaction is sent, there's no taking it back!"
 action = "broadcast this transaction to the network"
@@ -86,4 +84,4 @@ except:
 msg("Transaction sent: %s" % tx_id)
 
 of = "tx_{}[{}].txid".format(*metadata[:2])
-write_to_file(of, tx_id+"\n","transaction ID",True,True)
+write_data_to_file(of, tx_id+"\n","transaction ID",ask_overwrite=True)

+ 20 - 21
mmgen/main_txsign.py

@@ -101,7 +101,7 @@ Seed data supplied in files must have the following extensions:
 FMT CODES:
   {f}
 """.format(
-		f="\n  ".join(SeedSource.format_fmt_codes().split("\n")),
+		f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
 		g=g,pnm=pnm,pnl=pnl
 	)
 }
@@ -126,11 +126,11 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
 		if infiles:
 			ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
 		elif opt.in_fmt:
-			qmsg("Need seed data for seed ID %s" % seed_id)
+			qmsg("Need seed data for Seed ID %s" % seed_id)
 			ss = SeedSource()
-			msg("User input produced seed ID %s" % make_chksum_8(seed))
+			msg("User input produced Seed ID %s" % make_chksum_8(seed))
 		else:
-			msg("ERROR: No seed source found for seed ID: %s" % seed_id)
+			msg("ERROR: No seed source found for Seed ID: %s" % seed_id)
 			sys.exit(2)
 
 		saved_seeds[ss.seed.sid] = ss.seed.data
@@ -229,7 +229,7 @@ for the following non-{pnm} address{suf}:\n    {l}""".format(
 def parse_mmgen_keyaddr_file():
 	from mmgen.addr import AddrInfo
 	ai = AddrInfo(opt.mmgen_keys_from_file,has_keys=True)
-	vmsg("Found %s wif key%s for seed ID %s" %
+	vmsg("Found %s wif key%s for Seed ID %s" %
 			(ai.num_addrs, suf(ai.num_addrs,"k"), ai.seed_id))
 	# idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif)
 	return dict(
@@ -238,14 +238,10 @@ def parse_mmgen_keyaddr_file():
 
 def parse_keylist(from_file):
 	fn = opt.keys_from_file
-	d = get_data_from_file(fn,"non-{pnm} keylist".format(pnm=pnm))
-	enc_ext = get_extension(fn) == g.mmenc_ext
-	if enc_ext or not is_utf8(d):
-		if not enc_ext: qmsg("Keylist file appears to be encrypted")
-		from crypto import mmgen_decrypt_retry
-		d = mmgen_decrypt_retry(d,"encrypted keylist")
-	# Check for duplication with key-address file
-	keys_all = set(remove_comments(d.split("\n")))
+	from mmgen.crypto import mmgen_decrypt_file_maybe
+	dec = mmgen_decrypt_file_maybe(fn,"non-{} keylist file".format(pnm))
+	# Remove possible dups from key-address file
+	keys_all = set(remove_comments(dec.splitlines())) # DOS-safe
 	d = from_file['mmdata']
 	kawifs = [d[k][1] for k in d.keys()]
 	keys = [k for k in keys_all if k not in kawifs]
@@ -359,7 +355,7 @@ for tx_num,tx_file in enumerate(tx_files,1):
 
 	extra_sids = set(saved_seeds.keys()) - sids
 	if extra_sids:
-		msg("Unused seed ID%s: %s" %
+		msg("Unused Seed ID%s: %s" %
 			(suf(extra_sids,"k")," ".join(extra_sids)))
 
 	# Begin signing
@@ -378,13 +374,16 @@ for tx_num,tx_file in enumerate(tx_files,1):
 		if keypress_confirm("Edit transaction comment?"):
 			comment = get_tx_comment_from_user(comment)
 		outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
-		data = make_tx_data("{} {} {t}".format(*metadata[:2], t=make_timestamp()),
-				sig_tx['hex'], inputs_data, b2m_map, comment)
-		w = "signed transaction{}".format(tx_num_str)
-		if keypress_confirm("Save signed transaction?",default_yes=False):
-			write_to_file(outfile,data,w,(not opt.quiet),True,False)
-		else:
-			msg("Signed transaction not saved")
+		data = make_tx_data(
+				"{} {} {t}".format(*metadata[:2],
+				t=make_timestamp()),
+				sig_tx['hex'], inputs_data, b2m_map, comment
+			)
+		write_data_to_file(
+			outfile,data,
+			"signed transaction{}".format(tx_num_str),
+			ask_write_prompt="Save signed transaction?"
+		)
 	else:
 		msg_r("failed\nSome keys were missing.  ")
 		msg("Transaction %scould not be signed." % tx_num_str)

+ 1 - 1
mmgen/main_wallet.py

@@ -103,7 +103,7 @@ opts_data = {
 FMT CODES:
   {f}
 """.format(
-	f="\n  ".join(SeedSource.format_fmt_codes().split("\n")),
+	f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
 	pw_note=pw_note,
 	bw_note=("","\n\n" + bw_note)[int(bool(bw_note))]
 	)

+ 0 - 149
mmgen/mnemonic.py

@@ -1,149 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-mnemonic.py:  Mnemonic routines for the MMGen suite
-"""
-
-import sys
-from binascii import hexlify
-from mmgen.util import msg,msg_r,make_chksum_8,Vmsg,Msg
-from mmgen.crypto import get_random
-import mmgen.globalvars as g
-import mmgen.opt as opt
-
-wl_checksums = {
-	"electrum": '5ca31424',
-	"tirosh":   '1a5faeff'
-}
-
-# These are the only base-1626 specific configs:
-mn_base = 1626
-def mn2hex_pad(mn):     return len(mn) * 8 / 3
-def hex2mn_pad(hexnum): return len(hexnum) * 3 / 8
-
-# Universal base-conversion routines:
-def baseNtohex(base,words,wordlist,pad=0):
-	deconv = \
-		[wordlist.index(words[::-1][i])*(base**i) for i in range(len(words))]
-	ret = ("{:0%sx}" % pad).format(sum(deconv))
-	return "%s%s" % (('0' if len(ret) % 2 else ''), ret)
-
-def hextobaseN(base,hexnum,wordlist,pad=0):
-	num,ret = int(hexnum,16),[]
-	while num:
-		ret.append(num % base)
-		num /= base
-	return [wordlist[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
-
-def get_seed_from_mnemonic(mn,wl,silent=False,label=""):
-
-	if len(mn) not in g.mn_lens:
-		msg("Invalid mnemonic (%i words).  Allowed numbers of words: %s" %
-				(len(mn),", ".join([str(i) for i in g.mn_lens])))
-		sys.exit(3)
-
-	for n,w in enumerate(mn,1):
-		if w not in wl:
-			msg("Invalid mnemonic: word #%s is not in the wordlist" % n)
-			sys.exit(3)
-
-	from binascii import unhexlify
-	hseed = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn))
-
-	rev = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed))
-	if rev != mn:
-		msg("ERROR: mnemonic recomputed from seed not the same as original")
-		msg("Recomputed mnemonic:\n%s" % " ".join(rev))
-		sys.exit(3)
-
-	if not silent:
-		msg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(hseed)))
-
-	return unhexlify(hseed)
-
-
-def get_mnemonic_from_seed(seed, wl, label="", verbose=False):
-
-	if len(seed)*8 not in g.seed_lens:
-		msg("%s: invalid seed length" % len(seed))
-		sys.exit(3)
-
-	from binascii import hexlify
-
-	hseed = hexlify(seed)
-	mn = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed))
-
-	if verbose:
-		msg("Wordlist:    %s"          % label.capitalize())
-		msg("Seed length: %s bits"     % (len(seed) * 8))
-		msg("Seed:        %s"          % hseed)
-		msg("mnemonic (%s words):\n%s" % (len(mn), " ".join(mn)))
-
-	rev = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn))
-	if rev != hseed:
-		msg("ERROR: seed recomputed from wordlist not the same as original seed!")
-		msg("Original seed:   %s" % hseed)
-		msg("Recomputed seed: %s" % rev)
-		sys.exit(3)
-
-	return mn
-
-
-def check_wordlist(wl,label):
-
-	Msg("Wordlist: %s" % label.capitalize())
-
-	from hashlib import sha256
-
-	Msg("Length:   %i words" % len(wl))
-	new_chksum = sha256(" ".join(wl)).hexdigest()[:8]
-
-	if new_chksum != wl_checksums[label]:
-		Msg("ERROR: Checksum mismatch.  Computed: %s, Saved: %s" % \
-			(new_chksum,wl_checksums[label]))
-		sys.exit(3)
-
-	Msg("Checksum: %s (matches)" % new_chksum)
-
-	if (sorted(wl) == wl):
-		Msg("List is sorted")
-	else:
-		Msg("ERROR: List is not sorted!")
-		sys.exit(3)
-
-from mmgen.mn_electrum  import words as el
-from mmgen.mn_tirosh    import words as tl
-wordlists = sorted(wl_checksums)
-
-def get_wordlist(wordlist):
-	wordlist = wordlist.lower()
-	if wordlist not in wordlists:
-		Msg('"%s": invalid wordlist.  Valid choices: %s' %
-			(wordlist,'"'+'" "'.join(wordlists)+'"'))
-		sys.exit(1)
-	return (el if wordlist == "electrum" else tl).strip().split("\n")
-
-def do_random_mn(nbytes,wordlist):
-	r = get_random(nbytes)
-	Vmsg("Seed: %s" % hexlify(r))
-	for wlname in (wordlists if wordlist == "all" else [wordlist]):
-		wl = get_wordlist(wlname)
-		mn = get_mnemonic_from_seed(r,wl,wordlist)
-		Vmsg("%s wordlist mnemonic:" % (wlname.capitalize()))
-		Msg(" ".join(mn))

+ 2 - 4
mmgen/rpc/proxy.py

@@ -104,10 +104,8 @@ class AuthServiceProxy(object):
 					'Authorization' : self.__authhdr,
 					'Content-type' : 'application/json' })
 		except:
-			from mmgen.util import msg
-			import sys
-			msg("Unable to connect to bitcoind")
-			sys.exit(2)
+			from mmgen.util import die,red
+			die(1,red("Unable to connect to bitcoind"))
 
 		httpresp = self.__conn.getresponse()
 		if httpresp is None:

+ 68 - 30
mmgen/seed.py

@@ -56,6 +56,7 @@ class Seed(MMGenObject):
 class SeedSource(MMGenObject):
 
 	desc = g.proj_name + " seed source"
+	file_mode = "text"
 	stdin_ok = False
 	ask_tty = True
 	no_tty  = False
@@ -64,7 +65,8 @@ class SeedSource(MMGenObject):
 
 	class SeedSourceData(MMGenObject): pass
 
-	def __new__(cls,fn=None,ss=None,ignore_in_fmt=False,passchg=False):
+	def __new__(cls,fn=None,ss=None,seed=None,
+				ignore_in_fmt=False,passchg=False):
 
 		def die_on_opt_mismatch(opt,sstype):
 			opt_sstype = cls.fmt_code_to_sstype(opt)
@@ -104,12 +106,13 @@ class SeedSource(MMGenObject):
 		else: # Called with no inputs - initialize with random seed
 			sstype = cls.fmt_code_to_sstype(opt.out_fmt)
 			me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet
-			me.seed = Seed()
+			me.seed = Seed(seed_bin=seed or None)
 			me.op = "new"
 
 		return me
 
-	def __init__(self,fn=None,ss=None,ignore_in_fmt=False,passchg=False):
+	def __init__(self,fn=None,ss=None,seed=None,
+					ignore_in_fmt=False,passchg=False):
 
 		self.ssdata = self.SeedSourceData()
 		self.msg = {}
@@ -132,12 +135,13 @@ class SeedSource(MMGenObject):
 			self._deformat_retry()
 			self._decrypt_retry()
 
-		m = (""," length %s" % self.seed.length)[int(self.seed.length != 256)]
-		qmsg("Valid %s for seed ID %s%s" % (self.desc,self.seed.sid,m))
+		m = ("",", seed length %s" % self.seed.length)[int(self.seed.length != 256)]
+		qmsg("Valid %s for Seed ID %s%s" % (self.desc,self.seed.sid,m))
 
 	def _get_data(self):
 		if hasattr(self,'infile'):
-			self.fmt_data = get_data_from_file(self.infile.name,self.desc)
+			self.fmt_data = get_data_from_file(self.infile.name,self.desc,
+								binary=self.file_mode=="binary")
 		else:
 			self.fmt_data = get_data_from_user(self.desc)
 
@@ -204,12 +208,17 @@ class SeedSource(MMGenObject):
 			] + sorted(d)]
 		return "\n".join(ret) + "\n"
 
+	def get_fmt_data(self):
+		self._format()
+		return self.fmt_data
+
 	def write_to_file(self):
 		self._format()
 		kwargs = {
 			'desc':     self.desc,
 			'ask_tty':  self.ask_tty,
-			'no_tty':   self.no_tty
+			'no_tty':   self.no_tty,
+			'binary':   self.file_mode == "binary"
 		}
 		write_data_to_file(self._filename(),self.fmt_data,**kwargs)
 
@@ -358,43 +367,70 @@ class Mnemonic (SeedSourceUnenc):
 	mn_base = 1626
 	wordlists = sorted(wl_checksums)
 
-	def _mn2hex_pad(self,mn): return len(mn) * 8 / 3
-	def _hex2mn_pad(self,hexnum): return len(hexnum) * 3 / 8
+	@staticmethod
+	def _mn2hex_pad(mn): return len(mn) * 8 / 3
+
+	@staticmethod
+	def _hex2mn_pad(hexnum): return len(hexnum) * 3 / 8
 
-	def _baseNtohex(self,base,words,wl,pad=0):
+	@staticmethod
+	def baseNtohex(base,words,wl,pad=0):
 		deconv =  [wl.index(words[::-1][i])*(base**i)
 					for i in range(len(words))]
 		ret = ("{:0%sx}" % pad).format(sum(deconv))
 		return ('','0')[len(ret) % 2] + ret
 
-	def _hextobaseN(self,base,hexnum,wl,pad=0):
+	@staticmethod
+	def hextobaseN(base,hexnum,wl,pad=0):
 		num,ret = int(hexnum,16),[]
 		while num:
 			ret.append(num % base)
 			num /= base
 		return [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
 
-	def _get_wordlist(self,wordlist=g.default_wordlist):
-		wordlist = wordlist.lower()
-		if wordlist not in self.wordlists:
+	@classmethod
+	def hex2mn(cls,hexnum,wordlist):
+		wl = cls.get_wordlist(wordlist)
+		return cls.hextobaseN(cls.mn_base,hexnum,wl,cls._hex2mn_pad(hexnum))
+
+	@classmethod
+	def mn2hex(cls,mn,wordlist):
+		wl = cls.get_wordlist(wordlist)
+		return cls.baseNtohex(cls.mn_base,mn,wl,cls._mn2hex_pad(mn))
+
+	@classmethod
+	def get_wordlist(cls,wordlist=None):
+		wordlist = wordlist or g.default_wordlist
+		if wordlist not in cls.wordlists:
 			die(1,'"%s": invalid wordlist.  Valid choices: %s' %
-				(wordlist,'"'+'" "'.join(self.wordlists)+'"'))
+				(wordlist,'"'+'" "'.join(cls.wordlists)+'"'))
+
+		return __import__("mmgen.mn_"+wordlist,fromlist=["words"]).words.split()
+
+	@classmethod
+	def check_wordlist(cls,wlname):
+
+		wl = cls.get_wordlist(wlname)
+		Msg("Wordlist: %s\nLength: %i words" % (capfirst(wlname),len(wl)))
+		new_chksum = sha256(" ".join(wl)).hexdigest()[:8]
 
-		if wordlist == "electrum":
-			from mmgen.mn_electrum  import words
-		elif wordlist == "tirosh":
-			from mmgen.mn_tirosh    import words
+		if (sorted(wl) == wl):
+			Msg("List is sorted")
 		else:
-			die(3,"Internal error: unknown wordlist")
+			die(3,"ERROR: List is not sorted!")
 
-		return words.strip().split("\n")
+		compare_chksums(
+			new_chksum,"generated checksum",
+			cls.wl_checksums[wlname],"saved checksum",
+			die_on_fail=True)
+		Msg("Checksum %s matches" % new_chksum)
 
 	def _format(self):
-		wl = self._get_wordlist()
+		wl = self.get_wordlist()
 		seed_hex = self.seed.hexdata
-		mn = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
+		mn = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
 
-		ret = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
+		ret = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
 		# Internal error, so just die on fail
 		compare_or_die(ret,"recomputed seed",
 						seed_hex,"original",e="Internal error")
@@ -405,7 +441,7 @@ class Mnemonic (SeedSourceUnenc):
 	def _deformat(self):
 
 		mn = self.fmt_data.split()
-		wl = self._get_wordlist()
+		wl = self.get_wordlist()
 
 		if len(mn) not in g.mn_lens:
 			msg("Invalid mnemonic (%i words).  Allowed numbers of words: %s" %
@@ -417,9 +453,9 @@ class Mnemonic (SeedSourceUnenc):
 				msg("Invalid mnemonic: word #%s is not in the wordlist" % n)
 				return False
 
-		seed_hex = self._baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
+		seed_hex = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
 
-		ret = self._hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
+		ret = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
 
 		# Internal error, so just die
 		compare_or_die(" ".join(ret),"recomputed mnemonic",
@@ -580,7 +616,7 @@ class Wallet (SeedSourceEnc):
 
 			return True
 
-		lines = self.fmt_data.rstrip().split("\n")
+		lines = self.fmt_data.splitlines()
 		if not check_master_chksum(lines,self.desc): return False
 
 		d = self.ssdata
@@ -723,6 +759,7 @@ class Brainwallet (SeedSourceEnc):
 
 class IncogWallet (SeedSourceEnc):
 
+	file_mode = "binary"
 	fmt_codes = "mmincog","incog","icg","i"
 	desc = "incognito data"
 	ext = "mmincog"
@@ -742,7 +779,7 @@ 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
+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': " %s hash preset"
@@ -841,7 +878,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 			return False
 
 	def _verify_seed_oldfmt(self,seed):
-		m = "Seed ID: %s.  Is the seed ID correct?" % make_chksum_8(seed)
+		m = "Seed ID: %s.  Is the Seed ID correct?" % make_chksum_8(seed)
 		if keypress_confirm(m, True):
 			return seed
 		else:
@@ -878,6 +915,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 
 class IncogWalletHex (IncogWallet):
 
+	file_mode = "text"
 	desc = "hex incognito data"
 	fmt_codes = "mmincox","incox","incog_hex","xincog","ix","xi"
 	ext = "mmincox"

+ 3 - 3
mmgen/share/Opts.py

@@ -28,9 +28,9 @@ def print_help(opts_data):
 	print ("  %-"+pn_len+"s %s") % (pn.upper()+":", opts_data['desc'].strip())
 	print ("  %-"+pn_len+"s %s %s")%("USAGE:", pn, opts_data['usage'].strip())
 	sep = "\n    "
-	print "  OPTIONS:"+sep+"%s" % sep.join(opts_data['options'].strip().split("\n"))
+	print "  OPTIONS:"+sep+"%s" % sep.join(opts_data['options'].strip().splitlines())
 	if "notes" in opts_data:
-		print "  %s" % "\n  ".join(opts_data['notes'][1:-1].split("\n"))
+		print "  %s" % "\n  ".join(opts_data['notes'][1:-1].splitlines())
 
 
 def process_opts(argv,opts_data,short_opts,long_opts):
@@ -86,7 +86,7 @@ def parse_opts(argv,opts_data,opt_filter=None):
 	pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)"
 	od,skip = [],True
 
-	for l in opts_data['options'].strip().split("\n"):
+	for l in opts_data['options'].strip().splitlines():
 		m = re.match(pat,l)
 		if m:
 			skip = True if (opt_filter and m.group(1) not in opt_filter) else False

+ 12 - 8
mmgen/test.py

@@ -22,7 +22,7 @@ test.py:  Shared routines for the test suites
 
 import sys,os
 from binascii import hexlify
-from mmgen.util import msg,write_to_file,red,green
+from mmgen.util import msg,write_data_to_file,red,green
 import mmgen.opt as opt
 
 def cleandir(d):
@@ -49,16 +49,20 @@ def mk_tmpdir(cfg):
 def get_tmpfile_fn(cfg,fn):
 	return os.path.join(cfg['tmpdir'],fn)
 
-def write_to_tmpfile(cfg,fn,data,mode='wb'):
-	write_to_file(os.path.join(cfg['tmpdir'],fn),data,silent=True,mode=mode)
+def write_to_tmpfile(cfg,fn,data,binary=False):
+	write_data_to_file(
+		os.path.join(cfg['tmpdir'],fn),
+		data,
+		silent=True,
+		binary=binary
+	)
 
-def read_from_tmpfile(cfg,fn):
+def read_from_file(fn,binary=False):
 	from mmgen.util import get_data_from_file
-	return get_data_from_file(os.path.join(cfg['tmpdir'],fn),silent=True)
+	return get_data_from_file(fn,silent=True,binary=binary)
 
-def read_from_file(fn):
-	from mmgen.util import get_data_from_file
-	return get_data_from_file(fn,silent=True)
+def read_from_tmpfile(cfg,fn,binary=False):
+	return read_from_file(os.path.join(cfg['tmpdir'],fn),binary=binary)
 
 def ok():
 	if opt.verbose or opt.exact_output:

+ 53 - 45
mmgen/tool.py

@@ -252,8 +252,9 @@ def usage(cmd):
 help = usage
 
 def hexdump(infile, cols=8, line_nums=True):
-	Msg(pretty_hexdump(get_data_from_file(infile,dash=True,silent=True),
-			cols=cols, line_nums=line_nums))
+	Msg(pretty_hexdump(
+			get_data_from_file(infile,dash=True,silent=True,binary=True),
+				cols=cols,line_nums=line_nums))
 
 def unhexdump(infile):
 	if sys.platform[:3] == "win":
@@ -316,42 +317,54 @@ def wif2addr(wif,compressed=False):
 	addr = bitcoin.privnum2addr(int(s_enc,16),compressed)
 	Vmsg_r("Addr: "); Msg(addr)
 
-from mmgen.mnemonic import *
+wordlists = "electrum","tirosh"
+dfl_wordlist = "electrum"
 
-def mn_rand128(wordlist="electrum"): do_random_mn(16,wordlist)
-def mn_rand192(wordlist="electrum"): do_random_mn(24,wordlist)
-def mn_rand256(wordlist="electrum"): do_random_mn(32,wordlist)
+from mmgen.seed import Mnemonic
+def do_random_mn(nbytes,wordlist):
+	hexrand = ba.hexlify(get_random(nbytes))
+	Vmsg("Seed: %s" % hexrand)
+	for wlname in (wordlists if wordlist == "all" else [wordlist]):
+		if wordlist == "all":
+			Msg("%s mnemonic:" % (wlname.capitalize()))
+		mn = Mnemonic.hex2mn(hexrand,wordlist=wlname)
+		Msg(" ".join(mn))
 
-def hex2mn(s,wordlist="electrum"):
-	import mmgen.mnemonic
-	wl = get_wordlist(wordlist)
-	Msg(" ".join(get_mnemonic_from_seed(ba.unhexlify(s), wl, wordlist)))
+def mn_rand128(wordlist=dfl_wordlist): do_random_mn(16,wordlist)
+def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist)
+def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist)
 
-def mn2hex(s,wordlist="electrum"):
-	import mmgen.mnemonic
-	wl = get_wordlist(wordlist)
-	Msg(ba.hexlify(get_seed_from_mnemonic(s.split(),wl,True)))
+def hex2mn(s,wordlist=dfl_wordlist):
+	mn = Mnemonic.hex2mn(s,wordlist)
+	Msg(" ".join(mn))
+
+def mn2hex(s,wordlist=dfl_wordlist):
+	hexnum = Mnemonic.mn2hex(s.split(),wordlist)
+	Msg(hexnum)
 
 def b32tohex(s):
 	b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
-	import mmgen.mnemonic
-	Msg(baseNtohex(32,s,b32a))
+	Msg(Mnemonic.baseNtohex(32,s,b32a))
 
 def hextob32(s):
 	b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
-	import mmgen.mnemonic
-	Msg("".join(hextobaseN(32,s,b32a)))
+	Msg("".join(Mnemonic.hextobaseN(32,s,b32a)))
 
-def mn_stats(wordlist="electrum"):
-	l = get_wordlist(wordlist)
-	check_wordlist(l,wordlist)
+def mn_stats(wordlist=dfl_wordlist):
+	Mnemonic.check_wordlist(wordlist)
 
-def mn_printlist(wordlist="electrum"):
-	wl = get_wordlist(wordlist)
+def mn_printlist(wordlist=dfl_wordlist):
+	wl = Mnemonic.get_wordlist(wordlist)
 	Msg("\n".join(wl))
 
-def id8(infile): Msg(make_chksum_8(get_data_from_file(infile,dash=True,silent=True)))
-def id6(infile): Msg(make_chksum_6(get_data_from_file(infile,dash=True,silent=True)))
+def id8(infile):
+	Msg(make_chksum_8(
+		get_data_from_file(infile,dash=True,silent=True,binary=True)
+	))
+def id6(infile):
+	Msg(make_chksum_6(
+		get_data_from_file(infile,dash=True,silent=True,binary=True)
+	))
 def str2id6(s):  Msg(make_chksum_6("".join(s.split())))
 
 # List MMGen addresses and their balances:
@@ -478,7 +491,7 @@ def hexlify(s):
 
 def sha256x2(s, file_input=False, hex_input=False):
 	from hashlib import sha256
-	if file_input:  b = get_data_from_file(s)
+	if file_input:  b = get_data_from_file(s,binary=True)
 	elif hex_input: b = decode_pretty_hexdump(s)
 	else:           b = s
 	Msg(sha256(sha256(b).digest()).hexdigest())
@@ -506,32 +519,27 @@ def hex2wif(hexpriv,compressed=False):
 
 
 def encrypt(infile,outfile="",hash_preset=""):
-	data = get_data_from_file(infile,"data for encryption")
+	data = get_data_from_file(infile,"data for encryption",binary=True)
 	enc_d = mmgen_encrypt(data,"user data",hash_preset)
-	if outfile == '-':
-		write_to_stdout(enc_d,"encrypted data")
-	else:
-		if not outfile:
-			outfile = os.path.basename(infile) + "." + g.mmenc_ext
-		write_to_file(outfile,enc_d,"encrypted data",True,True)
+	if not outfile:
+		outfile = "%s.%s" % (os.path.basename(infile),g.mmenc_ext)
+
+	write_data_to_file(outfile,enc_d,"encrypted data",binary=True)
 
 
 def decrypt(infile,outfile="",hash_preset=""):
-	enc_d = get_data_from_file(infile,"encrypted data")
+	enc_d = get_data_from_file(infile,"encrypted data",binary=True)
 	while True:
 		dec_d = mmgen_decrypt(enc_d,"user data",hash_preset)
 		if dec_d: break
 		msg("Trying again...")
-	if outfile == '-':
-		write_to_stdout(dec_d,"decrypted data",ask_terminal=not opt.quiet)
-	else:
-		if not outfile:
-			outfile = os.path.basename(infile)
-			if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
-				outfile = outfile[:-len(g.mmenc_ext)-1]
-			else:
-				outfile = outfile + ".dec"
-		write_to_file(outfile, dec_d, "decrypted data",True,True)
+
+	if not outfile:
+		o = os.path.basename(infile)
+		outfile = remove_extension(o,g.mmenc_ext)
+		if outfile == o: outfile += ".dec"
+
+	write_data_to_file(outfile,dec_d,"decrypted data",binary=True)
 
 
 def find_incog_data(filename,iv_id,keep_searching=False):
@@ -618,4 +626,4 @@ def rand2file(outfile, nbytes, threads=4, silent=False):
 	q2.join()
 	f.close()
 
-def bytespec(s): Msg(parse_nbytes(s))
+def bytespec(s): Msg(str(parse_nbytes(s)))

+ 51 - 195
mmgen/util.py

@@ -29,11 +29,12 @@ import mmgen.globalvars as g
 
 pnm = g.proj_name
 
-_red,_grn,_yel,_cya,_reset = \
-	["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0"]
+_red,_grn,_yel,_cya,_reset,_grnbg = \
+	["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0","30;102"]
 
 def red(s):     return _red+s+_reset
 def green(s):   return _grn+s+_reset
+def grnbg(s):    return _grnbg+s+_reset
 def yellow(s):  return _yel+s+_reset
 def cyan(s):    return _cya+s+_reset
 def nocolor(s): return s
@@ -43,13 +44,13 @@ def start_mscolor():
 		global red,green,yellow,cyan,nocolor
 		import os
 		if "MMGEN_NOMSCOLOR" in os.environ:
-			red = green = yellow = cyan = nocolor
+			red = green = yellow = cyan = grnbg = nocolor
 		else:
 			try:
 				import colorama
 				colorama.init(strip=True,convert=True)
 			except:
-				red = green = yellow = cyan = nocolor
+				red = green = yellow = cyan = grnbg = nocolor
 
 def msg(s):    sys.stderr.write(s+"\n")
 def msg_r(s):  sys.stderr.write(s)
@@ -144,7 +145,12 @@ def suf(arg,suf_type):
 		return "" if n == 1 else "s"
 
 def get_extension(f):
-	return os.path.splitext(f)[1][1:]
+	a,b = os.path.splitext(f)
+	return ('',b[1:])[int(len(b) > 1)]
+
+def remove_extension(f,e):
+	a,b = os.path.splitext(f)
+	return (f,a)[int(len(b) > 1 and b[1:] == e)]
 
 def make_chksum_N(s,nchars,sep=False):
 	if nchars%4 or not (4 <= nchars <= 64): return False
@@ -208,6 +214,11 @@ def is_utf8(s):
 	except: return False
 	else: return True
 
+def is_ascii(s):
+	try: s.decode("ascii")
+	except: return False
+	else: return True
+
 def match_ext(addr,ext):
 	return addr.split(".")[-1] == ext
 
@@ -235,7 +246,8 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
 
 def decode_pretty_hexdump(data):
 	from string import hexdigits
-	lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")]
+	pat = r'^[%s]+:\s+' % hexdigits
+	lines = [re.sub(pat,'',l) for l in data.splitlines()]
 	try:
 		return unhexlify("".join(("".join(lines).split())))
 	except:
@@ -270,20 +282,12 @@ def compare_or_die(val1, desc1, val2, desc2, e="Error"):
 	dmsg("%s OK (%s)" % (capfirst(desc2),val2))
 	return True
 
-def get_default_wordlist():
-
-	wl_id = g.default_wordlist
-	if wl_id == "electrum": from mmgen.mn_electrum import words as wl
-	elif wl_id == "tirosh": from mmgen.mn_tirosh   import words as wl
-	return wl.strip().split("\n")
-
 def open_file_or_exit(filename,mode):
 	try:
 		f = open(filename, mode)
 	except:
-		op = "reading" if 'r' in mode else "writing"
-		msg("Unable to open file '%s' for %s" % (filename,op))
-		sys.exit(2)
+		op = ("writing","reading")[int('r' in mode)]
+		die(2,"Unable to open file '%s' for %s" % (filename,op))
 	return f
 
 
@@ -408,19 +412,6 @@ def confirm_or_exit(message, question, expect="YES"):
 		die(2,"Exiting at user request")
 
 
-def write_to_stdout(data, desc, ask_terminal=True):
-	if sys.stdout.isatty() and ask_terminal:
-		confirm_or_exit("",'output {} to screen'.format(desc))
-	elif not sys.stdout.isatty():
-		try:
-			of = os.readlink("/proc/%d/fd/1" % os.getpid())
-			of_maybe = os.path.relpath(of)
-			of = of if of_maybe.find(os.path.pardir) == 0 else of_maybe
-			msg("Redirecting output to file '%s'" % of)
-		except:
-			msg("Redirecting output to file")
-	sys.stdout.write(data)
-
 # New function
 def write_data_to_file(
 		outfile,
@@ -428,19 +419,26 @@ def write_data_to_file(
 		desc="data",
 		ask_write=False,
 		ask_write_prompt="",
-		ask_write_default_yes=False,
+		ask_write_default_yes=True,
 		ask_overwrite=True,
 		ask_tty=True,
 		no_tty=False,
-		silent=False
+		silent=False,
+		binary=False
 	):
-	if opt.stdout or not sys.stdout.isatty():
+
+	if silent: ask_tty = ask_overwrite = False
+	if opt.quiet: ask_overwrite = False
+
+	if ask_write_default_yes == False or ask_write_prompt:
+		ask_write = True
+
+	if opt.stdout or not sys.stdout.isatty() or outfile in ('','-'):
 		qmsg("Output to STDOUT requested")
-		write_ok = False
 		if sys.stdout.isatty():
 			if no_tty:
 				die(2,"Printing %s to screen is not allowed" % desc)
-			if ask_tty:
+			if ask_tty and not opt.quiet:
 				confirm_or_exit("",'output %s to screen' % desc)
 		else:
 			try:    of = os.readlink("/proc/%d/fd/1" % os.getpid()) # Linux
@@ -450,7 +448,7 @@ def write_data_to_file(
 				if of[:5] == "pipe:":
 					if no_tty:
 						die(2,"Writing %s to pipe is not allowed" % desc)
-					if ask_tty:
+					if ask_tty and not opt.quiet:
 						confirm_or_exit("",'output %s to pipe' % desc)
 						msg("")
 				of2,pd = os.path.relpath(of),os.path.pardir
@@ -459,138 +457,41 @@ def write_data_to_file(
 			else:
 				msg("Redirecting output to file")
 
+		if binary and sys.platform[:3] == "win":
+			import msvcrt
+			msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
+
 		sys.stdout.write(data)
 	else:
 		if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
 
 		if ask_write:
+			if not ask_write_prompt: ask_write_prompt = "Save %s?" % desc
 			if not keypress_confirm(ask_write_prompt,
 						default_yes=ask_write_default_yes):
-				die(1,"Exiting at user request")
+				die(1,"%s not saved" % capfirst(desc))
 
 		hush = False
-		if file_exists(outfile):
-			if ask_overwrite and not silent:
-					q = "File '%s' already exists\nOverwrite?" % outfile
-					confirm_or_exit("",q)
-					msg("Overwriting file '%s'" % outfile)
+		if file_exists(outfile) and ask_overwrite:
+			q = "File '%s' already exists\nOverwrite?" % outfile
+			confirm_or_exit("",q)
+			msg("Overwriting file '%s'" % outfile)
 			hush = True
 
-		f = open_file_or_exit(outfile,'wb')
+		f = open_file_or_exit(outfile,'w'+('','b')[int(binary)])
 		try:
 			f.write(data)
 		except:
-			if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile))
-			sys.exit(2)
+			die(2,"Failed to write %s to file '%s'" % (desc,outfile))
 		f.close
 
-		if not hush:
+		if not (hush or silent):
 			msg("%s written to file '%s'" % (capfirst(desc),outfile))
 
 		return True
 
-
-def write_to_file(
-		outfile,
-		data,
-		desc="data",
-		confirm_overwrite=False,
-		verbose=False,
-		silent=False,
-		mode='wb'
-	):
-
-	if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
-
-	try:    os.stat(outfile)
-	except: pass
-	else:
-		if confirm_overwrite:
-			q = "File '%s' already exists\nOverwrite?" % outfile
-			confirm_or_exit("",q)
-		else:
-			if not silent: msg("Overwriting file '%s'" % outfile)
-
-	f = open_file_or_exit(outfile,mode)
-	try:
-		f.write(data)
-	except:
-		if not silent: msg("Failed to write %s to file '%s'" % (desc,outfile))
-		sys.exit(2)
-	f.close
-
-	if verbose: msg("%s written to file '%s'" % (capfirst(desc),outfile))
-	return True
-
-
-def write_to_file_or_stdout(outfile, data,  desc="data"):
-
-	if opt.stdout or not sys.stdout.isatty():
-		write_to_stdout(data, desc)
-	else:
-		write_to_file(outfile,data,desc,not opt.quiet,True)
-
-
 from mmgen.bitcoin import b58decode_pad,b58encode_pad
 
-def display_control_data(label,metadata,hash_preset,salt,enc_seed):
-	Msg("WALLET DATA")
-	fs = "  {:18} {}"
-	pw_empty = "yes" if metadata[3] == "E" else "no"
-	for i in (
-		("Label:",               label),
-		("Seed ID:",             metadata[0].upper()),
-		("Key  ID:",             metadata[1].upper()),
-		("Seed length:",         "%s bits (%s bytes)" %
-				(metadata[2],int(metadata[2])/8)),
-		("Scrypt params:",  "Preset '%s' (%s)" % (hash_preset,
-				" ".join([str(i) for i in get_hash_params(hash_preset)]))),
-		("Passphrase empty?", pw_empty.capitalize()),
-		("Timestamp:",           "%s UTC" % metadata[4]),
-	): Msg(fs.format(*i))
-
-	fs = "  {:6} {}"
-	for i in (
-		("Salt:",    ""),
-		("  b58:",      b58encode_pad(salt)),
-		("  hex:",      hexlify(salt)),
-		("Encrypted seed:", ""),
-		("  b58:",      b58encode_pad(enc_seed)),
-		("  hex:",      hexlify(enc_seed))
-	): Msg(fs.format(*i))
-
-
-def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed):
-
-	seed_id = make_chksum_8(seed)
-	seed_len = str(len(seed)*8)
-	pw_status = "NE" if len(passwd) else "E"
-	hash_preset = opt.hash_preset
-	label = opt.label or "No Label"
-	metadata = seed_id.lower(),key_id.lower(),seed_len,\
-		pw_status,make_timestamp()
-	sf  = b58encode_pad(salt)
-	esf = b58encode_pad(enc_seed)
-
-	lines = (
-		label,
-		"{} {} {} {} {}".format(*metadata),
-		"{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)),
-		"{} {}".format(make_chksum_6(sf),  split_into_cols(4,sf)),
-		"{} {}".format(make_chksum_6(esf), split_into_cols(4,esf))
-	)
-
-	chk = make_chksum_6(" ".join(lines))
-	outfile="{}-{}[{},{}].{}".format(
-		seed_id,key_id,seed_len,hash_preset,g.wallet_ext)
-
-	d = "\n".join((chk,)+lines)+"\n"
-	write_to_file(outfile,d,"wallet",not opt.quiet,True)
-
-	if opt.debug:
-		display_control_data(label,metadata,hash_preset,salt,enc_seed)
-
-
 def _check_mmseed_format(words):
 
 	valid = False
@@ -638,50 +539,6 @@ def _check_chksum_6(chk,val,desc,infile):
 	dmsg("%s checksum passed: %s" % (capfirst(desc),chk))
 
 
-def get_data_from_wallet(infile,silent=False):
-
-	# Don't make this a qmsg: User will be prompted for passphrase and must see
-	# the filename.
-	if not silent and not opt.quiet:
-		msg("Getting {pnm} wallet data from file '{f}'".format(pnm=pnm,f=infile))
-
-	f = open_file_or_exit(infile, 'r')
-
-	lines = [i.strip() for i in f.readlines()]
-	f.close()
-
-	_check_wallet_format(infile, lines)
-
-	label = lines[1]
-
-	metadata = lines[2].split()
-
-	for i in 0,1: metadata[i] = metadata[i].upper()
-
-	hd = lines[3].split()
-	hash_preset = hd[0][:-1]
-	hash_params = [int(i) for i in hd[1:]]
-
-	if hash_params != get_hash_params(hash_preset):
-		msg("Hash parameters '%s' don't match hash preset '%s'" %
-				(" ".join(hash_params), hash_preset))
-		sys.exit(9)
-
-	res = {}
-	for i,key in (4,"salt"),(5,"enc_seed"):
-		l = lines[i].split()
-		val = "".join(l[1:])
-		_check_chksum_6(l[0], val, key, infile)
-		res[key] = b58decode_pad(val)
-		if res[key] == False:
-			msg("Invalid b58 number: %s" % val)
-			sys.exit(9)
-
-	_check_chksum_6(lines[0], " ".join(lines[1:]), "Master", infile)
-
-	return label,metadata,hash_preset,res['salt'],res['enc_seed']
-
-
 def get_words_from_user(prompt):
 	# split() also strips
 	words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
@@ -719,7 +576,7 @@ def get_lines_from_file(infile,desc="",trim_comments=False):
 	if desc != "":
 		qmsg("Getting %s from file '%s'" % (desc,infile))
 	f = open_file_or_exit(infile,'r')
-	lines = f.read().splitlines()
+	lines = f.read().splitlines() # DOS-safe
 	f.close()
 	return remove_comments(lines) if trim_comments else lines
 
@@ -729,11 +586,11 @@ def get_data_from_user(desc="data",silent=False):
 	dmsg("User input: [%s]" % data)
 	return data
 
-def get_data_from_file(infile,desc="data",dash=False,silent=False):
+def get_data_from_file(infile,desc="data",dash=False,silent=False,binary=False):
 	if dash and infile == "-": return sys.stdin.read()
 	if not silent:
 		qmsg("Getting %s from file '%s'" % (desc,infile))
-	f = open_file_or_exit(infile,'rb')
+	f = open_file_or_exit(infile,'r'+('','b')[int(binary)])
 	data = f.read()
 	f.close()
 	return data
@@ -757,7 +614,7 @@ def get_seed_from_seed_data(words):
 			msg("Invalid b58 number: %s" % val)
 			return False
 
-		msg("Valid seed data for seed ID %s" % make_chksum_8(seed))
+		msg("Valid seed data for Seed ID %s" % make_chksum_8(seed))
 		return seed
 	else:
 		msg("Invalid checksum for {pnm} seed".format(pnm=pnm))
@@ -787,8 +644,7 @@ def get_mmgen_passphrase(desc,passchg=False):
 def get_bitcoind_passphrase(prompt):
 	if opt.passwd_file:
 		pwfile_reuse_warning()
-		return get_data_from_file(opt.passwd_file,
-				"passphrase").strip("\r\n")
+		return get_data_from_file(opt.passwd_file,"passphrase").strip("\r\n")
 	else:
 		return my_raw_input(prompt, echo=opt.echo_passphrase)
 

+ 0 - 1
setup.py

@@ -37,7 +37,6 @@ setup(
 			'mmgen.filename',
 			'mmgen.license',
 			'mmgen.mn_electrum',
-			'mmgen.mnemonic',
 			'mmgen.mn_tirosh',
 			'mmgen.obj',
 			'mmgen.opts',

File diff suppressed because it is too large
+ 403 - 251
test/test.py


+ 9 - 11
test/tooltest.py

@@ -9,7 +9,7 @@ os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
 import mmgen.opt as opt
-from mmgen.util import msg,msg_r,vmsg,vmsg_r,Msg,mmsg,mdie,start_mscolor
+from mmgen.util import *
 from collections import OrderedDict
 
 start_mscolor()
@@ -108,9 +108,7 @@ if opt.list_cmds:
 
 import binascii
 from mmgen.test import *
-from mmgen.util import get_data_from_file,write_to_file,get_lines_from_file
 from mmgen.tx import is_wif,is_btc_addr,is_b58_str
-from mmgen.mnemonic import get_seed_from_mnemonic
 
 class MMGenToolTestSuite(object):
 
@@ -192,12 +190,12 @@ class MMGenToolTestSuite(object):
 		return ret
 
 	def run_cmd_out(self,name,carg=None,Return=False,kwargs="",fn_idx="",extra_msg=""):
-		if carg: write_to_tmpfile(cfg,"%s%s.in" % (name,fn_idx),carg+"\n",mode='w')
+		if carg: write_to_tmpfile(cfg,"%s%s.in" % (name,fn_idx),carg+"\n")
 		ret = self.run_cmd(name,[carg] if carg else [],kwargs=kwargs,extra_msg=extra_msg)
 		if carg: vmsg("In:   " + repr(carg))
 		vmsg("Out:  " + repr(ret))
 		if ret:
-			write_to_tmpfile(cfg,"%s%s.out" % (name,fn_idx),ret+"\n",mode='w')
+			write_to_tmpfile(cfg,"%s%s.out" % (name,fn_idx),ret+"\n")
 			if Return: return ret
 			else:   ok()
 		else:
@@ -207,10 +205,10 @@ class MMGenToolTestSuite(object):
 	def run_cmd_randinput(self,name,strip=True):
 		s = os.urandom(128)
 		fn = name+".in"
-		write_to_tmpfile(cfg,fn,s)
+		write_to_tmpfile(cfg,fn,s,binary=True)
 		ret = self.run_cmd(name,[get_tmpfile_fn(cfg,fn)],strip=strip)
 		fn = name+".out"
-		write_to_tmpfile(cfg,fn,ret+"\n",mode='w')
+		write_to_tmpfile(cfg,fn,ret+"\n")
 		ok()
 		vmsg("Returned: %s" % ret)
 
@@ -244,14 +242,14 @@ class MMGenToolTestSuite(object):
 
 	def unhexdump(self,name,fn1,fn2):
 		ret = self.run_cmd(name,[fn2],strip=False)
-		orig = read_from_file(fn1)
+		orig = read_from_file(fn1,binary=True)
 		cmp_or_die(orig,ret)
 
 	def rand2file(self,name):
 		of = name + ".out"
 		dlen = 1024
 		self.run_cmd(name,[of,str(1024),"threads=4","silent=1"],strip=False)
-		d = read_from_tmpfile(cfg,of)
+		d = read_from_tmpfile(cfg,of,binary=True)
 		cmp_or_die(dlen,len(d))
 
 	def strtob58(self,name):       self.run_cmd_out(name,getrandstr(16))
@@ -297,8 +295,8 @@ class MMGenToolTestSuite(object):
 		for n,fi,fo,m in (1,f1,f2,""),(2,f3,f4,"from compressed"):
 			self.run_cmd_chk(name,fi,fo,extra_msg=m)
 	def privhex2addr(self,name,f1,f2):
-		key1 = read_from_file(f1)
-		key2 = read_from_file(f2)
+		key1 = read_from_file(f1).rstrip()
+		key2 = read_from_file(f2).rstrip()
 		for n,args in enumerate([[key1],[key2,"compressed=1"]]):
 			ret = self.run_cmd(name,args).rstrip()
 			iaddr = read_from_tmpfile(cfg,"randpair%s.out" % (n+1)).split()[-1]

Some files were not shown because too many files changed in this diff