diff --git a/data_files/mmgen.cfg b/data_files/mmgen.cfg new file mode 100644 index 00000000..6194046c --- /dev/null +++ b/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 diff --git a/wordlists/mn_wordlist.c b/data_files/mn_wordlist.c similarity index 100% rename from wordlists/mn_wordlist.c rename to data_files/mn_wordlist.c diff --git a/wordlists/mnemonic.py b/data_files/mnemonic.py similarity index 100% rename from wordlists/mnemonic.py rename to data_files/mnemonic.py diff --git a/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md b/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md index b2ecbe5a..fc9d0472 100644 --- a/doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md +++ b/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: +**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 '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 +> 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 diff --git a/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md b/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md index edfce233..2087ae40 100644 --- a/doc/wiki/install-mswin/Install-MMGen-and-Its-Dependencies-on-Microsoft-Windows.md +++ b/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 diff --git a/doc/wiki/install-mswin/Install-MMGen-on-Microsoft-Windows.md b/doc/wiki/install-mswin/Install-MMGen-on-Microsoft-Windows.md index 2fe9f155..5fd61c81 100644 --- a/doc/wiki/install-mswin/Install-MMGen-on-Microsoft-Windows.md +++ b/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: diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index 00db1351..3ef80ed8 100755 --- a/mmgen/bitcoin.py +++ b/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() diff --git a/mmgen/common.py b/mmgen/common.py index 0627ccbf..aacbcae8 100755 --- a/mmgen/common.py +++ b/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 * diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 773e917a..9a2ad121 100755 --- a/mmgen/crypto.py +++ b/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 = \ diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index f17edf93..b2016e02 100755 --- a/mmgen/globalvars.py +++ b/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 +# 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 -# Constants - these don't change at runtime +class g(object): -# 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' + def die(ev=0,s=''): + if s: sys.stderr.write(s+'\n') + sys.exit(ev) + # Variables - these might be altered at runtime: -proj_name = 'MMGen' -prog_name = os.path.basename(sys.argv[0]) -author = 'Philemon' -email = '' -Cdates = '2013-2016' + version = '0.8.7a' + release_date = 'November 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'), -) + proj_name = 'MMGen' + prog_name = os.path.basename(sys.argv[0]) + author = 'Philemon' + email = '' + Cdates = '2013-2016' -min_screen_width = 80 -minconf = 1 + user_entropy = '' + hash_preset = '3' + usr_randchars = 30 -# Global var sets user opt: -dfl_vars = 'minconf','seed_len','hash_preset','usr_randchars','debug','tx_confs','tx_fee_adj','tx_fee','key_generator' + tx_fee = BTCAmt('0.0003') + tx_fee_adj = 1.0 + tx_confs = 3 -# User opt sets global var: -usr_sets_global = ['testnet'] -required_opts += usr_sets_global + seed_len = 256 + http_timeout = 60 -keyconv_exec = 'keyconv' + # Constants - some of these might be overriden, but they don't change thereafter -mins_per_block = 9 -passwd_max_tries = 5 + 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' -max_urandchars = 80 -_x = os.getenv('MMGEN_MIN_URANDCHARS') -min_urandchars = int(_x) if _x and int(_x) else 10 + 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)) -seed_lens = 128,192,256 -mn_lens = [i / 32 * 3 for i in seed_lens] + 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') -mmenc_ext = 'mmenc' -salt_len = 16 -aesctr_iv_len = 16 -hincog_chk_len = 8 + 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())) -key_generators = 'python-ecdsa','keyconv','secp256k1' -key_generator = 3 # secp256k1 is default + 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' + ) -hash_presets = { + 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], -} + '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) +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) diff --git a/mmgen/license.py b/mmgen/license.py index 77381cbd..47b691b0 100755 --- a/mmgen/license.py +++ b/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 diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index e7ff062b..7fada94d 100755 --- a/mmgen/main_addrgen.py +++ b/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]
', '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, diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 392de726..a1da7f90 100755 --- a/mmgen/main_addrimport.py +++ b/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 diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index f865c9f4..6b56a029 100755 --- a/mmgen/main_tool.py +++ b/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': """ diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 5c2d2b93..99ad4f1b 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -32,6 +32,7 @@ opts_data = { 'usage': '[opts] ... [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': """ diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index a42074c7..7daa0063 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -29,9 +29,9 @@ opts_data = { 'usage': '[opts] ', '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 """ } diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index a310f7d1..7d75ab7c 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -33,37 +33,35 @@ opts_data = { 'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()), 'usage': '[opts] ... [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(), diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index d049f819..c87d56da 100755 --- a/mmgen/main_wallet.py +++ b/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), diff --git a/mmgen/obj.py b/mmgen/obj.py index 9ec5565f..b13e948b 100755 --- a/mmgen/obj.py +++ b/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 diff --git a/mmgen/opts.py b/mmgen/opts.py index 0492f75d..83135156 100755 --- a/mmgen/opts.py +++ b/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:') diff --git a/mmgen/rpc.py b/mmgen/rpc.py index a1a720b7..82eae433 100755 --- a/mmgen/rpc.py +++ b/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") diff --git a/mmgen/seed.py b/mmgen/seed.py index 7282fe6e..2fb39f8a 100755 --- a/mmgen/seed.py +++ b/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'): diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index fe569ea2..2a205607 100755 --- a/mmgen/share/Opts.py +++ b/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 + od_all = [] - 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 + 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['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 + 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 diff --git a/mmgen/term.py b/mmgen/term.py index 4bc75f3c..32463ee3 100755 --- a/mmgen/term.py +++ b/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 diff --git a/mmgen/util.py b/mmgen/util.py index 96055141..7501d63a 100755 --- a/mmgen/util.py +++ b/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() diff --git a/scripts/tx-old2new.py b/scripts/tx-old2new.py index 1d99b935..5d9fe8dd 100755 --- a/scripts/tx-old2new.py +++ b/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: diff --git a/setup.py b/setup.py index a41de442..a572fe50 100755 --- a/setup.py +++ b/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', diff --git a/test/test.py b/test/test.py index f0d3cf41..e2b369cb 100755 --- a/test/test.py +++ b/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') diff --git a/test/tooltest.py b/test/tooltest.py index 0aa681fd..6913dc5b 100755 --- a/test/tooltest.py +++ b/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: