Browse Source

Long (common) opts added for setting global vars; display with '--longhelp'.

Datadir '~/.mmgen' and config file 'mmgen.cfg' added:
  * Global vars are now overriden in this order:
    1) config file
    2) environmental variables beginning with 'MMGEN_' (listed in globalvars.py)
    3) command line

Always get user entropy, even for non-critical randomness, unless '-r0'.

rpcuser,rpcpassword now override cookie authentication, as with Core.

RPC to remote daemon now supported with '--rpc-host' option.
philemon 8 years ago
parent
commit
456cc1f76c

+ 43 - 0
data_files/mmgen.cfg

@@ -0,0 +1,43 @@
+# Configuration file for the MMGen suite
+# Everything following a '#' is ignored
+
+################
+# User options #
+################
+
+# Uncomment to suppress the GPL license prompt:
+# no_license true
+
+# Uncomment to disable color output:
+# color false
+
+# Uncomment to use testnet instead of mainnet:
+# testnet true
+
+# Set the RPC host (the host bitcoind is running on):
+# rpc_host localhost
+
+# Set the default hash preset:
+# hash_preset 3
+
+# Set the default number of entropy characters to get from user.
+# Must be between 10 and 80.
+# A value of 0 disables user entropy, but this is not recommended:
+# usr_randchars 30
+
+# Set the default transaction fee in BTC:
+# tx_fee 0.0003
+
+# Set the transaction fee adjustment factor. Auto-calculated fees are
+# multiplied by this value:
+# tx_fee_adj 1.0
+
+#####################################################################
+# The following options are probably of interest only to developers #
+#####################################################################
+
+# Uncomment to display lots of debugging information:
+# debug true
+
+# Set the timeout for RPC connections:
+# http_timeout 60

+ 0 - 0
wordlists/mn_wordlist.c → data_files/mn_wordlist.c


+ 0 - 0
wordlists/mnemonic.py → data_files/mnemonic.py


+ 15 - 10
doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md

@@ -20,7 +20,9 @@ Install the secp256k1 library:
 Install MMGen:
 
 		$ git clone https://github.com/mmgen/mmgen.git
-		$ cd mmgen; sudo ./setup.py install
+		$ cd mmgen
+		$ git checkout -b stable stable_linux
+		$ sudo ./setup.py install
 
 Install vanitygen (optional):
 
@@ -34,15 +36,18 @@ Install bitcoind:
 > To install prebuilt binaries, click [here][01].  To install from source,
 > click [here][02].
 
-**NB:** If your offline machine is already disconnected from the Internet,
-do the following:
-
-> From your online machine, download the 'python-pip' package from Debian or
-> Ubuntu and the Python packages from pypi.python.org/pypi/<packagename>.
-> Transfer these files and the git repositories you've cloned to your offline
-> computer using a USB stick or other means at your disposal.  Now install
-> 'python-pip' with 'sudo dpkg -i', unpack each Python module and install it
-> using 'sudo ./setup.py install', and install MMGen and vanitygen from the
+**NB:** Naturally, your offline machine must be connected to the Internet to
+retrieve and install the above packages as described above.  If your offline
+machine is already offline and you wish to leave it that way, then you'll be
+forced to take roughly the following steps:
+
+> From your online machine, download the Debian/Ubuntu packages and their
+> dependencies manually from packages.debian.org or packages.ubuntu.com, and the
+> Python packages from pypi.python.org/pypi/<packagename>.  Transfer these
+> files and the git repositories you've cloned to your offline computer using a
+> USB stick or other means at your disposal.  Install the Debian/Ubuntu packages
+> with 'sudo dpkg -i', unpack each Python module and install it using 'sudo
+> ./setup.py install', and install MMGen and the secp256k1 library from the
 > copied git repositories as described above.
 
 Congratulations, your installation is now complete!  Now proceed to [**Getting

+ 9 - 2
doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md

@@ -1,3 +1,8 @@
+***Warning: the MMGen installation process on Windows is not for the faint of
+heart, success is not guaranteed and the user experience is less than optimal.
+You're urged to use the prebuilt [MMGenLive][20] USB image instead.  It's now
+the preferred way for all non-Linux users to run MMGen.***
+
 ##### Note: The following instructions assume you'll be unpacking all archives to `C:\`, the root directory on most Windows installations.  If you choose to unpack to another location, the `cd` commands must be adjusted accordingly.
 
 #### 1. Install the Python interpreter:
@@ -83,9 +88,9 @@ Grab the [tarball][14] and unpack it.  At the MSYS prompt, run:
 
 #### 8. Install MMGen:
 
-Get the [zip archive][10] from GitHub and unpack it.  At the MSYS prompt, run:
+Get the [zip archive][10b] from GitHub and unpack it.  At the MSYS prompt, run:
 
-		$ cd /c/mmgen-master
+		$ cd /c/mmgen-stable_mswin
 		$ sudo ./setup.py install
 
 Type:
@@ -107,7 +112,9 @@ a new MSYS window to update your path.
 [06]: http://www.openssl.org/source/openssl-1.0.1g.tar.gz
 [05]: http://www.openssl.org/source/
 [10]: https://github.com/mmgen/mmgen/archive/master.zip
+[10b]: https://github.com/mmgen/mmgen/archive/stable_mswin.zip
 [11]: http://slproweb.com/download/Win32OpenSSL-1_0_1f.exe
 [12]: http://www.openssl.org/related/binaries.html
 [13]: Getting-Started-with-MMGen
 [14]: https://pypi.python.org/pypi/colorama
+[20]: https://github.com/mmgen/MMGenLive

+ 3 - 3
doc/wiki/install-mswin/Install-MMGen-on-Microsoft-Windows.md

@@ -1,7 +1,7 @@
 ***Warning: the MMGen installation process on Windows is not for the faint of
-heart, and the user experience is less than optimal.  You're urged to use the
-prebuilt [MMGenLive][00] USB image instead.  It's now the preferred way for all
-non-Linux users to run MMGen.***
+heart, success is not guaranteed and the user experience is less than optimal.
+You're urged to use the prebuilt [MMGenLive][00] USB image instead.  It's now
+the preferred way for all non-Linux users to run MMGen.***
 
 Install MMGen on Windows by completing the following three steps:
 

+ 1 - 1
mmgen/bitcoin.py

@@ -51,7 +51,7 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 # The 'zero address':
 # 1111111111111111111114oLvT2 (use step2 = ('0' * 40) to generate)
 
-import mmgen.globalvars as g
+from mmgen.globalvars import g
 
 def pubhex2hexaddr(pubhex):
 	step1 = sha256(unhexlify(pubhex)).digest()

+ 1 - 1
mmgen/common.py

@@ -21,7 +21,7 @@ common.py:  Common imports for all MMGen scripts
 """
 
 import sys
-import mmgen.globalvars as g
+from mmgen.globalvars import g
 import mmgen.opts as opts
 from mmgen.opts import opt
 from mmgen.util import *

+ 1 - 1
mmgen/crypto.py

@@ -186,7 +186,7 @@ def _get_random_data_from_user(uchars):
 def get_random(length):
 	from Crypto import Random
 	os_rand = Random.new().read(length)
-	if g.use_urandchars and opt.usr_randchars:
+	if opt.usr_randchars:
 		from_what = 'OS random data'
 		if not g.user_entropy:
 			g.user_entropy = \

+ 189 - 93
mmgen/globalvars.py

@@ -20,101 +20,197 @@
 globalvars.py:  Constants and configuration options for the MMGen suite
 """
 
-version      = '0.8.7a'
-release_date = 'November 2016'
-
 import sys,os
-
-# Variables - these might be altered at runtime:
-
-user_entropy   = ''
-hash_preset    = '3'
-usr_randchars  = 30
-use_urandchars = False
-
 from mmgen.obj import BTCAmt
-tx_fee        = BTCAmt('0.0003')
-tx_fee_adj    = 1.0
-tx_confs      = 3
-
-seed_len     = 256
-http_timeout = 60
-
-# Constants - these don't change at runtime
-
-# os.getenv() returns None if environmental var is unset
-debug                = os.getenv('MMGEN_DEBUG')
-no_license           = os.getenv('MMGEN_NOLICENSE')
-bogus_wallet_data    = os.getenv('MMGEN_BOGUS_WALLET_DATA')
-disable_hold_protect = os.getenv('MMGEN_DISABLE_HOLD_PROTECT')
-color = (False,True)[sys.stdout.isatty() and not os.getenv('MMGEN_DISABLE_COLOR')]
-testnet = (False,True)[bool(os.getenv('MMGEN_TESTNET'))]
-testnet_name = 'testnet3'
-
-proj_name = 'MMGen'
-prog_name = os.path.basename(sys.argv[0])
-author    = 'Philemon'
-email     = '<mmgen-py@yandex.com>'
-Cdates    = '2013-2016'
-
-required_opts = [
-	'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
-	'show_hash_presets','label','keep_passphrase','keep_hash_preset',
-	'brain_params','b16','usr_randchars'
-]
-incompatible_opts = (
-	('quiet','verbose'),
-	('label','keep_label'),
-	('tx_id', 'info'),
-	('tx_id', 'terse_info'),
-	('batch', 'rescan'),
-)
-
-min_screen_width = 80
-minconf = 1
-
-# Global var sets user opt:
-dfl_vars = 'minconf','seed_len','hash_preset','usr_randchars','debug','tx_confs','tx_fee_adj','tx_fee','key_generator'
-
-# User opt sets global var:
-usr_sets_global = ['testnet']
-required_opts += usr_sets_global
-
-keyconv_exec = 'keyconv'
-
-mins_per_block   = 9
-passwd_max_tries = 5
-
-max_urandchars = 80
-_x = os.getenv('MMGEN_MIN_URANDCHARS')
-min_urandchars = int(_x) if _x and int(_x) else 10
-
-seed_lens = 128,192,256
-mn_lens = [i / 32 * 3 for i in seed_lens]
-
-mmenc_ext      = 'mmenc'
-salt_len       = 16
-aesctr_iv_len  = 16
-hincog_chk_len = 8
-
-key_generators = 'python-ecdsa','keyconv','secp256k1'
-key_generator = 3 # secp256k1 is default
-
-hash_presets = {
+
+# Global vars are set to dfl values in class g.
+# They're overridden in this order:
+#   1 - config file
+#   2 - environmental vars
+#   3 - command line
+
+class g(object):
+
+	def die(ev=0,s=''):
+		if s: sys.stderr.write(s+'\n')
+		sys.exit(ev)
+	# Variables - these might be altered at runtime:
+
+	version      = '0.8.7a'
+	release_date = 'November 2016'
+
+	proj_name = 'MMGen'
+	prog_name = os.path.basename(sys.argv[0])
+	author    = 'Philemon'
+	email     = '<mmgen-py@yandex.com>'
+	Cdates    = '2013-2016'
+
+	user_entropy   = ''
+	hash_preset    = '3'
+	usr_randchars  = 30
+
+	tx_fee        = BTCAmt('0.0003')
+	tx_fee_adj    = 1.0
+	tx_confs      = 3
+
+	seed_len     = 256
+	http_timeout = 60
+
+	# Constants - some of these might be overriden, but they don't change thereafter
+
+	debug                = False
+	no_license           = False
+	hold_protect         = True
+	color                = (False,True)[sys.stdout.isatty()]
+	testnet              = False
+	bogus_wallet_data    = ''
+	rpc_host             = 'localhost'
+	testnet_name         = 'testnet3'
+
+	for k in ('win','linux'):
+		if sys.platform[:len(k)] == k:
+			platform = k; break
+	else:
+		die(1,"'%s': platform not supported by %s\n" % (sys.platform,proj_name))
+
+	if os.getenv('HOME'):                             # Linux or MSYS
+		home_dir = os.getenv('HOME')
+	elif platform == 'win' and os.getenv('HOMEPATH'): # Windows:
+		home_dir = os.getenv('HOMEPATH')
+	else:
+		m = ('$HOME is not set','Neither $HOME nor %HOMEPATH% are set')[platform=='win']
+		die(2,m + '\nUnable to determine home directory')
+
+	data_dir = (os.path.join(home_dir,'Application Data',proj_name),
+				os.path.join(home_dir,'.'+proj_name.lower()))[bool(os.getenv('HOME'))]
+	bitcoin_data_dir = (os.path.join(home_dir,'Application Data','Bitcoin'),
+				os.path.join(home_dir,'.bitcoin'))[bool(os.getenv('HOME'))]
+	cfg_file = os.path.join(data_dir,'{}.cfg'.format(proj_name.lower()))
+
+	common_opts = ['color','no_license','rpc_host','testnet']
+	required_opts = [
+		'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
+		'show_hash_presets','label','keep_passphrase','keep_hash_preset',
+		'brain_params','b16','usr_randchars'
+	]
+	incompatible_opts = (
+		('quiet','verbose'),
+		('label','keep_label'),
+		('tx_id','info'),
+		('tx_id','terse_info'),
+		('batch','rescan'),
+	)
+	env_opts = (
+		'MMGEN_BOGUS_WALLET_DATA',
+		'MMGEN_DEBUG',
+		'MMGEN_DISABLE_COLOR',
+		'MMGEN_DISABLE_HOLD_PROTECT',
+		'MMGEN_MIN_URANDCHARS',
+		'MMGEN_NO_LICENSE',
+		'MMGEN_RPC_HOST',
+		'MMGEN_TESTNET'
+	)
+
+	min_screen_width = 80
+	minconf = 1
+
+	# Global var sets user opt:
+	global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
+						'tx_confs','tx_fee_adj','tx_fee','key_generator']
+
+	keyconv_exec = 'keyconv'
+
+	mins_per_block   = 9
+	passwd_max_tries = 5
+
+	max_urandchars = 80
+	min_urandchars = 10
+
+	seed_lens = 128,192,256
+	mn_lens = [i / 32 * 3 for i in seed_lens]
+
+	mmenc_ext      = 'mmenc'
+	salt_len       = 16
+	aesctr_iv_len  = 16
+	hincog_chk_len = 8
+
+	key_generators = 'python-ecdsa','keyconv','secp256k1' # 1,2,3
+	key_generator  = 3 # secp256k1 is default
+
+	hash_presets = {
 #   Scrypt params:
 #   ID    N   p  r
 # N is a power of two
-	'1': [12, 8, 1],
-	'2': [13, 8, 4],
-	'3': [14, 8, 8],
-	'4': [15, 8, 12],
-	'5': [16, 8, 16],
-	'6': [17, 8, 20],
-	'7': [18, 8, 24],
-}
-
-for k in ('win','linux'):
-	if sys.platform[:len(k)] == k: platform = k; break
-else:
-	sys.stderr.write("'%s': platform not supported by %s\n" % (sys.platform,proj_name))
-	sys.exit(1)
+		'1': [12, 8, 1],
+		'2': [13, 8, 4],
+		'3': [14, 8, 8],
+		'4': [15, 8, 12],
+		'5': [16, 8, 16],
+		'6': [17, 8, 20],
+		'7': [18, 8, 24],
+	}
+
+def create_data_dir(g):
+	from mmgen.util import msg,die
+	try:
+		os.listdir(g.data_dir)
+	except:
+		try:
+			os.mkdir(g.data_dir,0700)
+		except:
+			die(2,"ERROR: unable to read or create '{}'".format(g.data_dir))
+
+def get_data_from_config_file(g):
+	from mmgen.util import msg,die
+	# https://wiki.debian.org/Python:
+	#   Debian (Ubuntu) sys.prefix is '/usr' rather than '/usr/local, so add 'local'
+	# TODO - test for Windows
+	# This must match the configuration in setup.py
+	data = u''
+	try:
+		with open(g.cfg_file,'rb') as f: data = f.read().decode('utf8')
+	except:
+		cfg_template = os.path.join(*([sys.prefix]
+					+ ([''],['local','share'])[bool(os.getenv('HOME'))]
+					+ [g.proj_name.lower(),os.path.basename(g.cfg_file)]))
+		try:
+			with open(cfg_template,'rb') as f: template_data = f.read()
+		except:
+			msg("WARNING: configuration template not found at '{}'".format(cfg_template))
+		else:
+			try:
+				with open(g.cfg_file,'wb') as f: f.write(template_data)
+				os.chmod(g.cfg_file,0600)
+			except:
+				die(2,"ERROR: unable to write to datadir '{}'".format(g.data_dir))
+	return data
+
+def override_from_cfg_file(g,cfg_data):
+	from mmgen.util import die,strip_comments,set_for_type
+	cvars = ('color','debug','hash_preset','http_timeout','no_license','rpc_host',
+			'testnet','tx_fee','tx_fee_adj','usr_randchars')
+	import re
+	for n,l in enumerate(cfg_data.splitlines(),1): # DOS-safe
+		l = strip_comments(l)
+		if l == '': continue
+		m = re.match(r'(\w+)\s+(\S+)$',l)
+		if not m: die(2,"Parse error in file '{}', line {}".format(g.cfg_file,n))
+		name,val = m.groups()
+		if name in cvars:
+			setattr(g,name,set_for_type(val,getattr(g,name),name,src=g.cfg_file))
+		else:
+			die(2,"'{}': unrecognized option in '{}'".format(name,g.cfg_file))
+
+def override_from_env(g):
+	from mmgen.util import set_for_type
+	for name in g.env_opts:
+		idx,invert_bool = ((6,False),(14,True))[name[:14]=='MMGEN_DISABLE_']
+		val = os.getenv(name) # os.getenv() returns None if env var is unset
+		if val:
+			gname = name[idx:].lower()
+			setattr(g,gname,set_for_type(val,getattr(g,gname),name,invert_bool))
+
+create_data_dir(g)
+cfg_data = get_data_from_config_file(g)
+override_from_cfg_file(g,cfg_data)
+override_from_env(g)

+ 1 - 1
mmgen/license.py

@@ -20,7 +20,7 @@
 license.py:  Copyright notice and text of GPLv3
 """
 
-import mmgen.globalvars as g
+from mmgen.globalvars import g
 
 warning = """
   {pnm} Copyright (C) {g.Cdates} by {g.author} {g.email}.  This

+ 21 - 19
mmgen/main_addrgen.py

@@ -34,7 +34,7 @@ By default, both addresses and secret keys are generated.
 """.strip()
 else:
 	gen_what = 'addresses'
-	opt_filter = 'hbcdeiHOKlpzPqSv-'
+	opt_filter = 'hbcdeiHOKlpzPqrSv-'
 	note1 = """
 If available, the external 'keyconv' program will be used for address
 generation.
@@ -46,29 +46,31 @@ opts_data = {
                   mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
 	'usage':'[opts] [infile] <address range or list>',
 	'options': """
--h, --help            Print this help message.
--A, --no-addresses    Print only secret keys, no addresses.
--c, --print-checksum  Print address list checksum and exit.
--d, --outdir=      d  Output files to directory 'd' instead of working dir.
--e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry.
--i, --in-fmt=      f  Input is from wallet format 'f' (see FMT CODES below).
+-h, --help            Print this help message
+--, --longhelp        Print help message for long options (common options)
+-A, --no-addresses    Print only secret keys, no addresses
+-c, --print-checksum  Print address list checksum and exit
+-d, --outdir=      d  Output files to directory 'd' instead of working dir
+-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
+-i, --in-fmt=      f  Input is from wallet format 'f' (see FMT CODES below)
 -H, --hidden-incog-input-params=f,o  Read hidden incognito data from file
-                      'f' at offset 'o' (comma-separated).
--O, --old-incog-fmt   Specify old-format incognito input.
--K, --key-generator=m Use method 'm' for public key generation.
+                      'f' at offset 'o' (comma-separated)
+-O, --old-incog-fmt   Specify old-format incognito input
+-K, --key-generator=m Use method 'm' for public key generation
                       Options: {kgs} (default: {kg})
 -l, --seed-len=    l  Specify wallet seed length of 'l' bits.  This option
                       is required only for brainwallet and incognito inputs
-                      with non-standard (< {g.seed_len}-bit) seed lengths.
+                      with non-standard (< {g.seed_len}-bit) seed lengths
 -p, --hash-preset= p  Use the scrypt hash parameters defined by preset 'p'
-                      for password hashing (default: '{g.hash_preset}').
--z, --show-hash-presets Show information on available hash presets.
--P, --passwd-file= f  Get wallet passphrase from file 'f'.
--q, --quiet           Produce quieter output; suppress some warnings.
--S, --stdout          Print {what} to stdout.
---, --testnet         Generate testnet keys/addresses instead of mainnet ones
--v, --verbose         Produce more verbose output.
--x, --b16             Print secret keys in hexadecimal too.
+                      for password hashing (default: '{g.hash_preset}')
+-z, --show-hash-presets Show information on available hash presets
+-P, --passwd-file= f  Get wallet passphrase from file 'f'
+-q, --quiet           Produce quieter output; suppress some warnings
+-r, --usr-randchars=n Get 'n' characters of additional randomness from user
+                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+-S, --stdout          Print {what} to stdout
+-v, --verbose         Produce more verbose output
+-x, --b16             Print secret keys in hexadecimal too
 """.format(
 	seed_lens=', '.join([str(i) for i in g.seed_lens]),
 	pnm=g.proj_name,

+ 1 - 1
mmgen/main_addrimport.py

@@ -34,6 +34,7 @@ opts_data = {
 	'usage':'[opts] [mmgen address file]',
 	'options': """
 -h, --help         Print this help message
+--, --longhelp     Print help message for long options (common options)
 -b, --batch        Import all addresses in one RPC call.
 -l, --addrlist     Address source is a flat list of (non-MMGen) Bitcoin addresses
 -k, --keyaddr-file Address source is a key-address file
@@ -41,7 +42,6 @@ opts_data = {
 -r, --rescan       Rescan the blockchain.  Required if address to import is
                    on the blockchain and has a balance.  Rescanning is slow.
 -t, --test         Simulate operation; don't actually import addresses
---, --testnet      Use Bitcoin testnet instead of mainnet
 """,
 	'notes': """\n
 This command can also be used to update the comment fields of addresses already

+ 1 - 1
mmgen/main_tool.py

@@ -30,11 +30,11 @@ opts_data = {
 	'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -h, --help            Print this help message
+--, --longhelp        Print help message for long options (common options)
 -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})
---, --testnet         Use Bitcoin testnet instead of mainnet
 -v, --verbose         Produce more verbose output
 """.format(g=g),
 	'notes': """

+ 1 - 1
mmgen/main_txcreate.py

@@ -32,6 +32,7 @@ opts_data = {
 	'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ...',
 	'options': """
 -h, --help            Print this help message
+--, --longhelp        Print help message for long options (common options)
 -a, --tx-fee-adj=   f Adjust transaction fee by factor 'f' (see below)
 -B, --no-blank        Don't blank screen before displaying unspent outputs
 -c, --comment-file= f Source the transaction's comment from file 'f'
@@ -41,7 +42,6 @@ opts_data = {
 -m, --minconf=      n Minimum number of confirmations required to spend outputs (default: 1)
 -i, --info            Display unspent outputs and exit
 -q, --quiet           Suppress warnings; overwrite files without prompting
---, --testnet         Create transaction for Bitcoin testnet instead of mainnet
 -v, --verbose         Produce more verbose output
 """.format(g=g),
 	'notes': """

+ 1 - 1
mmgen/main_txsend.py

@@ -29,9 +29,9 @@ opts_data = {
 	'usage':   '[opts] <signed transaction file>',
 	'options': """
 -h, --help      Print this help message
+--, --longhelp  Print help message for long options (common options)
 -d, --outdir= d Specify an alternate directory 'd' for output
 -q, --quiet     Suppress warnings; overwrite files without prompting
---, --testnet   Use Bitcoin testnet instead of mainnet
 """
 }
 

+ 20 - 22
mmgen/main_txsign.py

@@ -33,37 +33,35 @@ opts_data = {
 	'desc':    'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
 	'usage':   '[opts] <transaction file>... [seed source]...',
 	'options': """
--h, --help            Print this help message.
--b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brain-
-                      wallet input.
--d, --outdir=      d  Specify an alternate directory 'd' for output.
--D, --tx-id           Display transaction ID and exit.
--e, --echo-passphrase Print passphrase to screen when typing it.
--i, --in-fmt=      f  Input is from wallet format 'f' (see FMT CODES below).
+-h, --help            Print this help message
+--, --longhelp        Print help message for long options (common options)
+-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brainwallet
+                      input
+-d, --outdir=      d  Specify an alternate directory 'd' for output
+-D, --tx-id           Display transaction ID and exit
+-e, --echo-passphrase Print passphrase to screen when typing it
+-i, --in-fmt=      f  Input is from wallet format 'f' (see FMT CODES below)
 -H, --hidden-incog-input-params=f,o  Read hidden incognito data from file
-                      'f' at offset 'o' (comma-separated).
--O, --old-incog-fmt   Specify old-format incognito input.
--l, --seed-len=    l  Specify wallet seed length of 'l' bits.  This option
+                      'f' at offset 'o' (comma-separated)
+-O, --old-incog-fmt   Specify old-format incognito input
+-l, --seed-len=    l  Specify wallet seed length of 'l' bits. This option
                       is required only for brainwallet and incognito inputs
                       with non-standard (< {g.seed_len}-bit) seed lengths.
 -p, --hash-preset=p   Use the scrypt hash parameters defined by preset 'p'
-                      for password hashing (default: '{g.hash_preset}').
--z, --show-hash-presets Show information on available hash presets.
+                      for password hashing (default: '{g.hash_preset}')
+-z, --show-hash-presets Show information on available hash presets
 -k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
--K, --key-generator=m Use method 'm' for public key generation.
+-K, --key-generator=m Use method 'm' for public key generation
                       Options: {kgs} (default: {kg})
 -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
                       address file (output of '{pnl}-keygen'). Permits
-                      online signing without an {pnm} seed source.
-                      The key-address file is also used to verify
-                      {pnm}-to-BTC mappings, so its checksum should
-                      be recorded by the user.
+                      online signing without an {pnm} seed source. The
+                      key-address file is also used to verify {pnm}-to-BTC
+                      mappings, so the user should record its checksum.
 -P, --passwd-file= f  Get {pnm} wallet or bitcoind passphrase from file 'f'
--q, --quiet           Suppress warnings; overwrite files without
-                      prompting
--I, --info            Display information about the transaction and exit.
--t, --terse-info      Like '--info', but produce more concise output.
---, --testnet         Transaction is for Bitcoin testnet rather than mainnet
+-q, --quiet           Suppress warnings; overwrite files without prompting
+-I, --info            Display information about the transaction and exit
+-t, --terse-info      Like '--info', but produce more concise output
 -v, --verbose         Produce more verbose output
 """.format(
 		g=g,pnm=pnm,pnl=pnm.lower(),

+ 25 - 25
mmgen/main_wallet.py

@@ -38,7 +38,7 @@ pw_note = opts.pw_note
 
 if invoked_as == 'gen':
 	desc = 'Generate an {pnm} wallet from a random seed'
-	opt_filter = 'ehdoJlLpPqrSvz'
+	opt_filter = 'ehdoJlLpPqrSvz-'
 	usage = '[opts]'
 	oaction = 'output'
 	nargs = 0
@@ -47,11 +47,11 @@ elif invoked_as == 'conv':
 	opt_filter = None
 elif invoked_as == 'chk':
 	desc = 'Check validity of an {pnm} wallet'
-	opt_filter = 'ehiHOlpPqrvz'
+	opt_filter = 'ehiHOlpPqrvz-'
 	iaction = 'input'
 elif invoked_as == 'passchg':
 	desc = 'Change the password, hash preset or label of an {pnm} wallet'
-	opt_filter = 'ehdiHkKOlLmpPqrSvz'
+	opt_filter = 'ehdiHkKOlLmpPqrSvz-'
 	iaction = 'input'
 	bw_note = ''
 else:
@@ -63,34 +63,34 @@ opts_data = {
 	'desc': desc.format(pnm=g.proj_name),
 	'usage': usage,
 	'options': """
--h, --help            Print this help message.
--d, --outdir=      d  Output files to directory 'd' instead of working dir.
--e, --echo-passphrase Echo passphrases and other user input to screen.
--i, --in-fmt=      f  {iaction} from wallet format 'f' (see FMT CODES below).
--o, --out-fmt=     f  {oaction} to wallet format 'f' (see FMT CODES below).
+-h, --help            Print this help message
+--, --longhelp        Print help message for long options (common options)
+-d, --outdir=      d  Output files to directory 'd' instead of working dir
+-e, --echo-passphrase Echo passphrases and other user input to screen
+-i, --in-fmt=      f  {iaction} from wallet format 'f' (see FMT CODES below)
+-o, --out-fmt=     f  {oaction} to wallet format 'f' (see FMT CODES below)
 -H, --hidden-incog-input-params=f,o  Read hidden incognito data from file
-                      'f' at offset 'o' (comma-separated).
--J, --hidden-incog-output-params=f,o  Write hidden incognito data to file
-                      'f' at offset 'o' (comma-separated).  If file 'f'
-                      doesn't exist, it will be created and filled with
-                      random data.
--O, --old-incog-fmt   Specify old-format incognito input.
--k, --keep-passphrase Reuse passphrase of input wallet for output wallet.
--K, --keep-hash-preset Reuse hash preset of input wallet for output wallet.
+                      'f' at offset 'o' (comma-separated)
+-J, --hidden-incog-output-params=f,o  Write hidden incognito data to file 'f'
+                      at offset 'o' (comma-separated).  File 'f' will be cre-
+                      ated and filled with random data if it doesn't exist.
+-O, --old-incog-fmt   Specify old-format incognito input
+-k, --keep-passphrase Reuse passphrase of input wallet for output wallet
+-K, --keep-hash-preset Reuse hash preset of input wallet for output wallet
 -l, --seed-len=    l  Specify wallet seed length of 'l' bits.  This option
                       is required only for brainwallet and incognito inputs
                       with non-standard (< {g.seed_len}-bit) seed lengths.
--L, --label=       l  Specify a label 'l' for output wallet.
--m, --keep-label      Reuse label of input wallet for output wallet.
+-L, --label=       l  Specify a label 'l' for output wallet
+-m, --keep-label      Reuse label of input wallet for output wallet
 -p, --hash-preset= p  Use the scrypt hash parameters defined by preset 'p'
-                      for password hashing (default: '{g.hash_preset}').
--z, --show-hash-presets Show information on available hash presets.
--P, --passwd-file= f  Get wallet passphrase from file 'f'.
--q, --quiet           Produce quieter output; suppress some warnings.
+                      for password hashing (default: '{g.hash_preset}')
+-z, --show-hash-presets Show information on available hash presets
+-P, --passwd-file= f  Get wallet passphrase from file 'f'
+-q, --quiet           Produce quieter output; suppress some warnings
 -r, --usr-randchars=n Get 'n' characters of additional randomness from user
-                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars}).
--S, --stdout          Write wallet data to stdout instead of file.
--v, --verbose         Produce more verbose output.
+                      (min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
+-S, --stdout          Write wallet data to stdout instead of file
+-v, --verbose         Produce more verbose output
 """.format(
 		g=g,
 		iaction=capfirst(iaction),

+ 3 - 2
mmgen/obj.py

@@ -92,6 +92,7 @@ class MMGenObject(object):
 
 		return repr(self) + '\n    ' + '\n    '.join(out)
 
+# Descriptor: https://docs.python.org/2/howto/descriptor.html
 class MMGenListItemAttr(object):
 	def __init__(self,name,dtype):
 		self.name = name
@@ -109,7 +110,7 @@ class MMGenListItem(MMGenObject):
 	addr = MMGenListItemAttr('addr','BTCAddr')
 	amt  = MMGenListItemAttr('amt','BTCAmt')
 	mmid = MMGenListItemAttr('mmid','MMGenID')
-	label = MMGenListItemAttr('label','MMGenLabel')
+	label = MMGenListItemAttr('label','MMGenAddrLabel')
 
 	attrs = ()
 	attrs_priv = ()
@@ -269,7 +270,7 @@ class Hilite(object):
 
 	@classmethod
 	def colorize(cls,s,color=True):
-		import mmgen.globalvars as g
+		from mmgen.globalvars import g
 		from mmgen.util import red,blue,green,yellow,pink,cyan,gray,orange,magenta
 		k = color if type(color) is str else cls.color # hack: override color with str value
 		return locals()[k](s) if (color or cls.color_always) and g.color else s

+ 49 - 39
mmgen/opts.py

@@ -23,7 +23,7 @@ import sys
 
 class opt(object): pass
 
-import mmgen.globalvars as g
+from mmgen.globalvars import g
 import mmgen.share.Opts
 from mmgen.util import *
 
@@ -58,34 +58,34 @@ def die_on_incompatible_opts(incompat_list):
 		if len(bad) > 1:
 			die(1,'Conflicting options: %s' % ', '.join([fmt_opt(b) for b in bad]))
 
-def _typeconvert_from_dfl(key):
-
-	global opt
-
-	gval = g.__dict__[key]
-	uval = opt.__dict__[key]
-	gtype = type(gval)
-
-	try:
-		setattr(opt,key,gtype(uval))
-	except:
-		d = {
-			'int':   'an integer',
-			'str':   'a string',
-			'float': 'a float',
-			'bool':  'a boolean value',
-		}
-		die(1, "'%s': invalid parameter for '--%s' option (not %s)" % (
-			uval,
-			key.replace('_','-'),
-			d[gtype.__name__]
-		))
-
-	if g.debug:
-		Msg('Opt overriden by user:\n    %-18s: %s' % (
-				key, ('%s -> %s' % (gval,uval))
-			))
-
+# def _typeconvert_from_dfl(key):
+#
+# 	global opt
+#
+# 	gval = g.__dict__[key]
+# 	uval = opt.__dict__[key]
+# 	gtype = type(gval)
+#
+# 	try:
+# 		setattr(opt,key,gtype(uval))
+# 	except:
+# 		d = {
+# 			'int':   'an integer',
+# 			'str':   'a string',
+# 			'float': 'a float',
+# 			'bool':  'a boolean value',
+# 		}
+# 		die(1, "'%s': invalid parameter for '--%s' option (not %s)" % (
+# 			uval,
+# 			key.replace('_','-'),
+# 			d[gtype.__name__]
+# 		))
+#
+# 	if g.debug:
+# 		Msg('Opt overriden by user:\n    %-18s: %s' % (
+# 				key, ('%s -> %s' % (gval,uval))
+# 			))
+#
 def fmt_opt(o): return '--' + o.replace('_','-')
 
 def _show_hash_presets():
@@ -96,17 +96,27 @@ def _show_hash_presets():
 		msg(fs.format("'%s'" % i, *g.hash_presets[i]))
 	msg('N = memory usage (power of two), p = iterations (rounds)')
 
+common_opts_data = """
+--, --color=b      Set 'b' to '0' to disable color output, '1' to enable
+--, --no-license   Suppress the GPL license prompt
+--, --rpc-host=h   Communicate with bitcoind running on host 'h'
+--, --testnet      Use testnet instead of mainnet
+"""
+
 def init(opts_data,add_opts=[],opt_filter=None):
 
 	if len(sys.argv) == 2 and sys.argv[1] == '--version':
 		print_version_info()
 		sys.exit()
 
+	opts_data['long_options'] = common_opts_data
+
 	uopts,args,short_opts,long_opts,skipped_opts = \
 		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter)
 
 	if g.debug:
 		d = (
+			('Cmdline',            ' '.join(sys.argv)),
 			('Short opts',         short_opts),
 			('Long opts',          long_opts),
 			('Skipped opts',       skipped_opts),
@@ -127,20 +137,21 @@ def init(opts_data,add_opts=[],opt_filter=None):
 
 	# Transfer uopts into opt, setting program's opts + required opts to None if not set by user
 	for o in [s.rstrip('=') for s in long_opts] + \
-			g.required_opts + add_opts + skipped_opts:
+			g.required_opts + add_opts + skipped_opts + g.common_opts:
 		setattr(opt,o,uopts[o] if o in uopts else None)
 
-	# User opt sets global var - do these here, before opt gets set from g.dfl_vars
-	if opt.usr_randchars: g.use_urandchars = True
-	for k in g.usr_sets_global:
-		if getattr(opt,k): setattr(g,k,True)
+	# User opt sets global var - do these here, before opt is set from g.global_sets_opt
+	for k in g.common_opts:
+		val = getattr(opt,k)
+		if val != None: setattr(g,k,set_for_type(val,getattr(g,k),'--'+k))
 
 	# If user opt is set, convert its type based on value in mmgen.globalvars (g)
 	# If unset, set it to default value in mmgen.globalvars (g)
 	setattr(opt,'set_by_user',[])
-	for k in g.dfl_vars:
-		if k in opt.__dict__ and opt.__dict__[k] != None:
-			_typeconvert_from_dfl(k)
+	for k in g.global_sets_opt:
+		if k in opt.__dict__ and getattr(opt,k) != None:
+#			_typeconvert_from_dfl(k)
+			setattr(opt,k,set_for_type(getattr(opt,k),getattr(g,k),'--'+k))
 			opt.set_by_user.append(k)
 		else:
 			setattr(opt,k,g.__dict__[k])
@@ -153,9 +164,8 @@ def init(opts_data,add_opts=[],opt_filter=None):
 		_show_hash_presets()
 		sys.exit()
 
-	if opt.debug: opt.verbose = True
-
 	if g.debug:
+		opt.verbose = True
 		a = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) != None]
 		b = [k for k in dir(opt) if k[:2] != '__' and getattr(opt,k) == None]
 		Msg('    Opts after processing:')

+ 4 - 4
mmgen/rpc.py

@@ -32,14 +32,14 @@ class BitcoinRPCConnection(object):
 
 	def __init__(
 				self,
-				host='localhost',port=(8332,18332)[g.testnet],
+				host=g.rpc_host,port=(8332,18332)[g.testnet],
 				user=None,passwd=None,auth_cookie=None,
 			):
 
-		if auth_cookie:
-			self.auth_str = auth_cookie
-		elif user and passwd:
+		if user and passwd:
 			self.auth_str = '{}:{}'.format(user,passwd)
+		elif auth_cookie:
+			self.auth_str = auth_cookie
 		else:
 			msg('Error: no Bitcoin RPC authentication method found')
 			if passwd: die(1,"'rpcuser' entry missing in bitcoin.conf")

+ 0 - 1
mmgen/seed.py

@@ -114,7 +114,6 @@ class SeedSource(MMGenObject):
 				self.msg.update(c._msg)
 
 		if hasattr(self,'seed'):
-			g.use_urandchars = True
 			self._encrypt()
 			return
 		elif hasattr(self,'infile'):

+ 30 - 33
mmgen/share/Opts.py

@@ -21,20 +21,21 @@ Opts.py:  Generic options handling
 """
 
 import sys, getopt
-from mmgen.util import pp_die,pp_msg # DEBUG
+# from mmgen.util import mdie,die,pp_die,pp_msg # DEBUG
 
 def usage(opts_data):
 	print 'USAGE: %s %s' % (opts_data['prog_name'], opts_data['usage'])
 	sys.exit(2)
 
-def print_help(opts_data):
+def print_help(opts_data,longhelp=False):
 	pn = opts_data['prog_name']
 	pn_len = str(len(pn)+2)
 	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 + sep.join(opts_data['options'].strip().splitlines())
-	if 'notes' in opts_data:
+	od_opts = opts_data[('options','long_options')[longhelp]].strip().splitlines()
+	sep,m,ls = (('\n    ','  OPTIONS:',''),('\n','  LONG OPTIONS:','    '))[longhelp]
+	print m + sep + ls + sep.join(od_opts)
+	if 'notes' in opts_data and not longhelp:
 		print '  ' + '\n  '.join(opts_data['notes'][1:-1].splitlines())
 
 
@@ -44,19 +45,17 @@ def process_opts(argv,opts_data,short_opts,long_opts):
 	opts_data['prog_name'] = os.path.basename(sys.argv[0])
 	long_opts  = [i.replace('_','-') for i in long_opts]
 
-#	pp_msg(long_opts) # DEBUG
-	try: cl_opts,args = getopt.getopt(argv[1:], short_opts.replace('-',''), long_opts)
+	so_str = short_opts.replace('-:','').replace('-','')
+	try: cl_opts,args = getopt.getopt(argv[1:], so_str, long_opts)
 	except getopt.GetoptError as err:
 		print str(err); sys.exit(2)
 
 	sopts_list = ':_'.join(['_'.join(list(i)) for i in short_opts.split(':')]).split('_')
 	opts = {}
 
-#	pp_msg(cl_opts) # DEBUG
-#	pp_msg(sopts_list) # DEBUG
-#	pp_die(args)
 	for opt, arg in cl_opts:
 		if   opt in ('-h','--help'): print_help(opts_data); sys.exit()
+		elif opt == '--longhelp':    print_help(opts_data,longhelp=True); sys.exit()
 		elif opt[:2] == '--' and opt[2:] in long_opts:
 			opts[opt[2:].replace('-','_')] = True
 		elif opt[:2] == '--' and opt[2:]+'=' in long_opts:
@@ -90,30 +89,28 @@ def parse_opts(argv,opts_data,opt_filter=None):
 
 	import re
 	pat = r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)'
-	od,skip = [],True
-
-	for l in opts_data['options'].strip().splitlines():
-		m = re.match(pat,l)
-		if m:
-			skip = (False,True)[bool(opt_filter) and m.group(1) not in opt_filter]
-			app = (['',''],[':','='])[m.group(3) == '=']
-			od.append(list(m.groups()) + app + [skip])
-		else:
-			if not skip: od[-1][3] += '\n' + l
-
-	opts_data['options'] = '\n'.join(
-		['{:<3} --{} {}'.format(
-			('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
-	)
-#	print opts_data['options']; sys.exit() # DEBUG
-# 	pp_die(od) # DEBUG
-	short_opts    = ''.join([d[0]+d[4] for d in od if d[6] == False])
-	long_opts     = [d[1].replace('-','_')+d[5] for d in od if d[6] == False]
-	skipped_opts  = [d[1].replace('-','_') for d in od if d[6] == True]
-#	pp_die(short_opts) # DEBUG
-#	pp_msg(long_opts) # DEBUG
+	od_all = []
+
+	for k in ('options','long_options'):
+		od,skip = [],True
+		for l in opts_data[k].strip().splitlines():
+			m = re.match(pat,l)
+			if m:
+				skip = (False,True)[bool(opt_filter) and m.group(1) not in opt_filter]
+				app = (['',''],[':','='])[m.group(3) == '=']
+				od.append(list(m.groups()) + app + [skip])
+			else:
+				if not skip: od[-1][3] += '\n' + l
+
+		opts_data[k] = '\n'.join(
+			['{:<3} --{} {}'.format(
+				('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
+		)
+		od_all += od
+	short_opts    = ''.join([d[0]+d[4] for d in od_all if d[6] == False])
+	long_opts     = [d[1].replace('-','_')+d[5] for d in od_all if d[6] == False]
+	skipped_opts  = [d[1].replace('-','_') for d in od_all if d[6] == True]
 
 	opts,args = process_opts(argv,opts_data,short_opts,long_opts)
-#	pp_die(opts) # DEBUG
 
 	return opts,args,short_opts,long_opts,skipped_opts

+ 10 - 10
mmgen/term.py

@@ -179,24 +179,24 @@ def mswin_dummy_flush(fd,termconst): pass
 try:
 	import tty,termios
 	from select import select
-	if g.disable_hold_protect:
-		get_char = _get_keypress_unix_raw
-		kb_hold_protect = _kb_hold_protect_unix_raw
-	else:
+	if g.hold_protect:
 		get_char = _get_keypress_unix
 		kb_hold_protect = _kb_hold_protect_unix
+	else:
+		get_char = _get_keypress_unix_raw
+		kb_hold_protect = _kb_hold_protect_unix_raw
 	get_terminal_size = _get_terminal_size_linux
 	myflush = termios.tcflush
 # call: myflush(sys.stdin, termios.TCIOFLUSH)
 except:
 	try:
 		import msvcrt,time
-		if g.disable_hold_protect:
-			get_char = _get_keypress_mswin_raw
-			kb_hold_protect = _kb_hold_protect_mswin_raw
-		else:
+		if g.hold_protect:
 			get_char = _get_keypress_mswin
 			kb_hold_protect = _kb_hold_protect_mswin
+		else:
+			get_char = _get_keypress_mswin_raw
+			kb_hold_protect = _kb_hold_protect_mswin_raw
 		get_terminal_size = _get_terminal_size_mswin
 		myflush = mswin_dummy_flush
 	except:
@@ -222,9 +222,9 @@ def do_pager(text):
 			shell = True
 			pagers = ['more']
 		else:                     # MSYS
-			environ['LESS'] = '-cR' # disable buggy line chopping
+			environ['LESS'] = '-cR -#1' # disable buggy line chopping
 	else:
-		environ['LESS'] = '-RS'
+		environ['LESS'] = '-RS -#1' # raw, chop, scroll right 1 char
 
 	if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
 		pagers = [environ['PAGER']] + pagers

+ 44 - 40
mmgen/util.py

@@ -25,10 +25,6 @@ from hashlib import sha256
 from binascii import hexlify,unhexlify
 from string import hexdigits
 
-import mmgen.globalvars as g
-
-pnm = g.proj_name
-
 # If 88- or 256-color support is compiled, the following apply.
 #    P s = 3 8 ; 5 ; P s -> Set foreground color to the second P s .
 #    P s = 4 8 ; 5 ; P s -> Set background color to the second P s .
@@ -56,19 +52,6 @@ def gray(s):    return _gry+s+_reset
 def magenta(s): return _mag+s+_reset
 def nocolor(s): return s
 
-def start_mscolor():
-	if g.platform == 'win':
-		global red,green,yellow,cyan,nocolor
-		import os
-		if 'MMGEN_NOMSCOLOR' in os.environ:
-			red = green = yellow = cyan = grnbg = nocolor
-		else:
-			try:
-				import colorama
-				colorama.init(strip=True,convert=True)
-			except:
-				red = green = yellow = cyan = grnbg = nocolor
-
 def msg(s):    sys.stderr.write(s+'\n')
 def msg_r(s):  sys.stderr.write(s)
 def Msg(s):    sys.stdout.write(s + '\n')
@@ -83,9 +66,11 @@ def mdie(*args):
 	sys.exit()
 
 def die(ev=0,s=''):
+	assert type(ev) == int
 	if s: sys.stderr.write(s+'\n')
 	sys.exit(ev)
 def Die(ev=0,s=''):
+	assert type(ev) == int
 	if s: sys.stdout.write(s+'\n')
 	sys.exit(ev)
 
@@ -101,6 +86,23 @@ def pp_msg(d):
 	import pprint
 	msg(pprint.PrettyPrinter(indent=4).pformat(d))
 
+def set_for_type(val,refval,desc,invert_bool=False,src=None):
+	src_str = (''," in '{}'".format(src))[bool(src)]
+	if type(refval) == bool:
+		v = str(val).lower()
+		if v in ('true','yes','1'):          ret = True
+		elif v in ('false','no','none','0'): ret = False
+		else: die(1,"'{}': invalid value for '{}'{} (must be of type '{}')".format(
+				val,desc,src_str,'bool'))
+		if invert_bool: ret = not ret
+	else:
+		try:
+			ret = type(refval)((val,not val)[invert_bool])
+		except:
+			die(1,"'{}': invalid value for '{}'{} (must be of type '{}')".format(
+				val,desc,src_str,type(refval).__name__))
+	return ret
+
 # From 'man dd':
 # c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
 # GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
@@ -265,18 +267,6 @@ def file_is_readable(f):
 	else:
 		return True
 
-def get_homedir():
-	if 'HOME' in os.environ:       # Linux
-		return os.environ['HOME']
-	elif 'HOMEPATH' in os.environ: # Windows:
-		return os.environ['HOMEPATH']
-	else:
-		msg('Neither $HOME nor %HOMEPATH% are set')
-		die(2,"Don't know where to look for bitcoin data directory")
-
-def get_datadir():
-	return (r'Application Data\Bitcoin','.bitcoin')['HOME' in os.environ]
-
 def get_from_brain_opt_params():
 	l,p = opt.from_brain.split(',')
 	return(int(l),p)
@@ -301,6 +291,27 @@ def decode_pretty_hexdump(data):
 		msg('Data not in hexdump format')
 		return False
 
+def strip_comments(line):
+	return re.sub(ur'\s+$',u'',re.sub(ur'#.*',u'',line,1))
+
+def remove_comments(lines):
+	return [m for m in [strip_comments(l) for l in lines] if m != '']
+
+from mmgen.globalvars import g
+
+def start_mscolor():
+	if g.platform == 'win':
+		global red,green,yellow,cyan,nocolor
+		import os
+		if 'MMGEN_NOMSCOLOR' in os.environ:
+			red = green = yellow = cyan = grnbg = nocolor
+		else:
+			try:
+				import colorama
+				colorama.init(strip=True,convert=True)
+			except:
+				red = green = yellow = cyan = grnbg = nocolor
+
 def get_hash_params(hash_preset):
 	if hash_preset in g.hash_presets:
 		return g.hash_presets[hash_preset] # N,p,r,buflen
@@ -509,14 +520,6 @@ def get_words(infile,desc,prompt):
 	else:
 		return get_words_from_user(prompt)
 
-def remove_comments(lines):
-	ret = []
-	for i in lines:
-		i = re.sub(ur'#.*',u'',i,1)
-		i = re.sub(ur'\s+$',u'',i)
-		if i: ret.append(i)
-	return ret
-
 def mmgen_decrypt_file_maybe(fn,desc=''):
 	d = get_data_from_file(fn,desc,binary=True)
 	have_enc_ext = get_extension(fn) == g.mmenc_ext
@@ -641,7 +644,7 @@ def do_license_msg(immed=False):
 
 def get_bitcoind_cfg_options(cfg_keys):
 
-	cfg_file = os.path.join(get_homedir(), get_datadir(), 'bitcoin.conf')
+	cfg_file = os.path.join(g.bitcoin_data_dir,'bitcoin.conf')
 
 	cfg = dict([(k,v) for k,v in [split2(str(line).translate(None,'\t '),'=')
 			for line in get_lines_from_file(cfg_file,'')] if k in cfg_keys]) \
@@ -653,7 +656,8 @@ def get_bitcoind_cfg_options(cfg_keys):
 
 def get_bitcoind_auth_cookie():
 
-	f = os.path.join(get_homedir(),get_datadir(),('',g.testnet_name)[g.testnet],'.cookie')
+	f = os.path.join(g.bitcoin_data_dir,('',g.testnet_name)[g.testnet],
+						('.','_')[g.platform=='win']+'cookie')
 
 	if file_is_readable(f):
 		return get_lines_from_file(f,'')[0]
@@ -663,7 +667,7 @@ def get_bitcoind_auth_cookie():
 def bitcoin_connection():
 
 	port = (8332,18332)[g.testnet]
-	host,user,passwd = 'localhost','rpcuser','rpcpassword'
+	host,user,passwd = g.rpc_host,'rpcuser','rpcpassword'
 	cfg = get_bitcoind_cfg_options((user,passwd))
 	auth_cookie = get_bitcoind_auth_cookie()
 

+ 4 - 4
scripts/tx-old2new.py

@@ -9,6 +9,7 @@ from mmgen.common import *
 from mmgen.tool import *
 from mmgen.tx import *
 from mmgen.bitcoin import *
+from mmgen.obj import MMGenTXLabel
 from mmgen.seed import *
 from mmgen.term import do_pager
 
@@ -24,7 +25,6 @@ import mmgen.opts
 cmd_args = opts.init(help_data)
 
 if len(cmd_args) != 1: opts.usage()
-
 def parse_tx_file(infile):
 
 	err_str,err_fmt = '','Invalid %s in transaction file'
@@ -57,9 +57,9 @@ def parse_tx_file(infile):
 							if comment == False:
 								err_str = 'encoded comment (not base58)'
 							else:
-								if is_valid_tx_comment(comment):
-									comment = comment.decode('utf8')
-								else:
+								try:
+									comment = MMGenTXLabel(comment)
+								except:
 									err_str = 'comment'
 
 	if err_str:

+ 7 - 2
setup.py

@@ -42,11 +42,11 @@ module1 = Extension(
 	include_dirs = ['/usr/local/include'],
 	)
 
-from mmgen.globalvars import version
+from mmgen.globalvars import g
 setup(
 		name         = 'mmgen',
 		description  = 'A complete Bitcoin offline/online wallet solution for the command line',
-		version      = version,
+		version      = g.version,
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
@@ -56,6 +56,11 @@ setup(
 		cmdclass     = { 'build_ext': my_build_ext },
 		# disable building of secp256k1 extension module on Windows
 		ext_modules = [module1] if sys.platform[:5] == 'linux' else [],
+		data_files = [('share/mmgen', [
+				'data_files/mmgen.cfg',     # source files must have 0644 mode
+				'data_files/mn_wordlist.c',
+				'data_files/mnemonic.py'
+				]),],
 		py_modules = [
 			'mmgen.__init__',
 			'mmgen.addr',

+ 17 - 8
test/test.py

@@ -319,6 +319,7 @@ cmd_group = OrderedDict()
 cmd_group['help'] = OrderedDict([
 #     test               description                  depends
 	['helpscreens',     (1,'help screens',             [],1)],
+	['longhelpscreens', (1,'help screens (--longhelp)',[],1)],
 ])
 
 cmd_group['main'] = OrderedDict([
@@ -505,7 +506,10 @@ meta_cmds = OrderedDict([
 
 del cmd_group
 
-if opt.testnet: os.environ['MMGEN_TESTNET'] = '1'
+add_spawn_args = ' '.join(['{} {}'.format(
+	'--'+k.replace('_','-'),
+	getattr(opt,k) if getattr(opt,k) != True else ''
+	) for k in 'testnet','rpc_host' if getattr(opt,k)]).split()
 
 if opt.profile: opt.names = True
 if opt.resume: opt.skip_deps = True
@@ -521,7 +525,7 @@ ni = bool(opt.non_interactive)
 
 # Disable MS color in spawned scripts due to bad interactions
 os.environ['MMGEN_NOMSCOLOR'] = '1'
-os.environ['MMGEN_NOLICENSE'] = '1'
+os.environ['MMGEN_NO_LICENSE'] = '1'
 os.environ['MMGEN_DISABLE_COLOR'] = '1'
 os.environ['MMGEN_MIN_URANDCHARS'] = '3'
 
@@ -688,7 +692,8 @@ class MMGenExpect(object):
 			if type(i) not in (str,unicode):
 				fs = 'Error: missing input files in cmd line?:\nName: {}\nCmd: {}\nCmd args: {}'
 				die(2,fs.format(name,mmgen_cmd,cmd_args))
-		cmd_str = mmgen_cmd + ' ' + ' '.join(cmd_args)
+		cmd_args = add_spawn_args + cmd_args
+		cmd_str = '{} {}'.format(mmgen_cmd,' '.join(cmd_args))
 		if opt.log:
 			log_fd.write(cmd_str+'\n')
 		if opt.verbose or opt.exact_output:
@@ -713,7 +718,7 @@ class MMGenExpect(object):
 			if opt.exact_output: self.p.logfile = sys.stdout
 
 	def license(self):
-		if 'MMGEN_NOLICENSE' in os.environ: return
+		if 'MMGEN_NO_LICENSE' in os.environ: return
 		p = "'w' for conditions and warranty info, or 'c' to continue: "
 		my_expect(self.p,p,'c')
 
@@ -1059,13 +1064,15 @@ class MMGenTestSuite(object):
 	def generate_cmd_deps(self,fdeps):
 		return [cfgs[str(n)]['dep_generators'][ext] for n,ext in fdeps]
 
-	def helpscreens(self,name):
+	def helpscreens(self,name,arg='--help'):
 		for s in scripts:
-			t = MMGenExpect(name,('mmgen-'+s),['--help'],
+			t = MMGenExpect(name,('mmgen-'+s),[arg],
 				extra_desc='(mmgen-%s)'%s,no_output=True)
 			if not ni:
 				t.read(); ok()
 
+	def longhelpscreens(self,name): self.helpscreens(name,arg='--longhelp')
+
 	def walletgen(self,name,seed_len=None):
 		write_to_tmpfile(cfg,pwfile,cfg['wpasswd']+'\n')
 		add_args = ([usr_rand_arg],
@@ -1406,7 +1413,7 @@ class MMGenTestSuite(object):
 			args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
 
 	def keyaddrgen(self,name,wf,pf=None,check_ref=False):
-		args = ['-d',cfg['tmpdir'],wf,cfg['addr_idx_list']]
+		args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
 		if ni:
 			m = "\nAnswer 'n' at the interactive prompt"
 			msg(grnbg(m))
@@ -1420,6 +1427,7 @@ class MMGenTestSuite(object):
 			refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk'])
 			return
 		t.expect('Encrypt key list? (y/N): ','y')
+		t.usr_rand(usr_rand_chars)
 		t.hash_preset('new key list','1')
 #		t.passphrase_new('new key list','kafile password')
 		t.passphrase_new('new key list',cfg['kapasswd'])
@@ -1531,8 +1539,9 @@ class MMGenTestSuite(object):
 			app = ['hash_preset=1']
 		else:
 			pre,app = [],[]
-		t = MMGenExpect(name,'mmgen-tool',pre+['-d',cfg['tmpdir'],'encrypt',infn]+app)
+		t = MMGenExpect(name,'mmgen-tool',pre+['-d',cfg['tmpdir'],usr_rand_arg,'encrypt',infn]+app)
 		if ni: return
+		t.usr_rand(usr_rand_chars)
 		t.hash_preset('user data','1')
 		t.passphrase_new('user data',tool_enc_passwd)
 		t.written_to_file('Encrypted data')

+ 6 - 3
test/tooltest.py

@@ -113,10 +113,10 @@ opts_data = {
 	'usage':'[options] [command]',
 	'options': """
 -h, --help          Print this help message
+--, --longhelp      Print help message for long options (common options)
 -l, --list-cmds     List and describe the tests and commands in the test suite
 -s, --system        Test scripts and modules installed on system rather than
                     those in the repo root
---, --testnet       Run on testnet rather than mainnet
 -v, --verbose       Produce more verbose output
 """,
 	'notes': """
@@ -126,7 +126,10 @@ If no command is given, the whole suite of tests is run.
 }
 
 cmd_args = opts.init(opts_data,add_opts=['exact_output','profile'])
-if opt.testnet: os.environ['MMGEN_TESTNET'] = '1'
+add_spawn_args = ' '.join(['{} {}'.format(
+	'--'+k.replace('_','-'),
+	getattr(opt,k) if getattr(opt,k) != True else ''
+	) for k in 'testnet','rpc_host' if getattr(opt,k)]).split()
 
 if opt.system: sys.path.pop(0)
 
@@ -187,7 +190,7 @@ class MMGenToolTestSuite(object):
 		if not opt.system:
 			mmgen_tool = os.path.join(os.curdir,mmgen_tool)
 
-		sys_cmd = ['python', mmgen_tool, '-d',cfg['tmpdir'], name] + tool_args + kwargs.split()
+		sys_cmd = ['python',mmgen_tool] + add_spawn_args + ['-r0','-d',cfg['tmpdir'],name] + tool_args + kwargs.split()
 		if extra_msg: extra_msg = '(%s)' % extra_msg
 		full_name = ' '.join([name]+kwargs.split()+extra_msg.split())
 		if not silent: