Browse Source

MMGen version 0.8.3

New features/improvements:

	* New native Bitcoin RPC library.
	* Support for cookie-based RPC authentication (new in Bitcoin Core v0.12.0).
	* Batch mode available when listing and importing addresses.
	* mmgen-tool listaddresses: 'addrs' argument allows you to specify an
	  address or range of addresses.

NOTE: if MMGen is already installed on your system, you must remove your
existing installation by hand before installing this new version.  On Linux,
this means deleting everything under the directory
'/usr/local/lib/python2.7/dist-packages/mmgen/'.  Also, if you did a 'git pull'
instead of a fresh clone, you must delete the 'build' directory in the
repository root before installing.

The 'mmgen-pywallet' utility has been removed.  It's no longer needed, as the
'bitcoin-cli dumpwallet' command (available since Core v0.9.0) provides
equivalent functionality.

The Windows port isn't being actively maintained at the moment.  Use at your own
risk, and report any problems on the Bitcointalk forum.
philemon 9 years ago
parent
commit
956eeab186
55 changed files with 2108 additions and 5138 deletions
  1. 10 11
      doc/wiki/install-linux/Install-MMGen-on-Debian-or-Ubuntu-Linux.md
  2. 44 33
      doc/wiki/using-mmgen/Getting-Started-with-MMGen.md
  3. 1 1
      mmgen-addrgen
  4. 1 1
      mmgen-addrimport
  5. 1 1
      mmgen-keygen
  6. 1 1
      mmgen-passchg
  7. 0 24
      mmgen-pywallet
  8. 1 1
      mmgen-tool
  9. 1 1
      mmgen-txcreate
  10. 1 1
      mmgen-txsend
  11. 1 1
      mmgen-txsign
  12. 1 1
      mmgen-walletchk
  13. 1 1
      mmgen-walletconv
  14. 1 1
      mmgen-walletgen
  15. 1 1
      mmgen/__init__.py
  16. 76 82
      mmgen/addr.py
  17. 21 21
      mmgen/bitcoin.py
  18. 8 3
      mmgen/common.py
  19. 59 63
      mmgen/crypto.py
  20. 2 2
      mmgen/filename.py
  21. 39 39
      mmgen/globalvars.py
  22. 2 2
      mmgen/license.py
  23. 8 8
      mmgen/main.py
  24. 25 30
      mmgen/main_addrgen.py
  25. 43 38
      mmgen/main_addrimport.py
  26. 0 1684
      mmgen/main_pywallet.py
  27. 11 15
      mmgen/main_tool.py
  28. 137 152
      mmgen/main_txcreate.py
  29. 19 24
      mmgen/main_txsend.py
  30. 76 83
      mmgen/main_txsign.py
  31. 41 42
      mmgen/main_wallet.py
  32. 1 1
      mmgen/mn_electrum.py
  33. 1 1
      mmgen/mn_tirosh.py
  34. 19 20
      mmgen/obj.py
  35. 65 71
      mmgen/opts.py
  36. 128 0
      mmgen/rpc.py
  37. 0 53
      mmgen/rpc/__init__.py
  38. 0 75
      mmgen/rpc/config.py
  39. 0 766
      mmgen/rpc/connection.py
  40. 0 168
      mmgen/rpc/data.py
  41. 0 203
      mmgen/rpc/exceptions.py
  42. 0 142
      mmgen/rpc/proxy.py
  43. 0 49
      mmgen/rpc/util.py
  44. 202 236
      mmgen/seed.py
  45. 36 32
      mmgen/share/Opts.py
  46. 1 1
      mmgen/share/__init__.py
  47. 29 30
      mmgen/term.py
  48. 8 8
      mmgen/test.py
  49. 171 174
      mmgen/tool.py
  50. 99 102
      mmgen/tx.py
  51. 189 178
      mmgen/util.py
  52. 6 15
      setup.py
  53. 35 22
      test/gentest.py
  54. 332 319
      test/test.py
  55. 153 104
      test/tooltest.py

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

@@ -12,17 +12,16 @@ Install the pexpect Python module:
 
 		$ sudo pip install pexpect
 
-		Note: pexpect v4.0.1 (the latest version as of this writing) is
-		BROKEN and will cause errors when running the test suite!
-		If this is the version you just installed on your system (examine the
-		output of 'pip freeze' to find out), then you must downgrade.  Note
-		that newer versions may be broken as well.  Version 3.1 is known to work
-		and can be found [here][03].  First, uninstall the broken pexpect:
-
-		$ sudo pip uninstall pexpect
-
-		Then download and unpack the v3.1 tarball, cd to the archive root and
-		run 'sudo python setup.py install' to install it.
+>> Note: pexpect v4.0.1 (the latest version as of this writing) is BROKEN and
+>> will cause errors when running the test suite!  If this is the version you
+>> just installed on your system (examine the output of 'pip freeze' to find
+>> out), then you must downgrade.  Note that newer versions may be broken as
+>> well.  Version 3.1 is known to work.  If this is the version on your system,
+>> then you may skip the next step.  Otherwise, download the [v3.1 tarball][03],
+>> unpack it, cd to the archive root and run:
+
+			$ sudo pip uninstall pexpect
+			$ sudo python setup.py install
 
 Install MMGen:
 

+ 44 - 33
doc/wiki/using-mmgen/Getting-Started-with-MMGen.md

@@ -229,55 +229,66 @@ confirmations, your transaction will be saved:
 Note that the transaction has a unique ID, and the non-change spend amount of
 6.6 BTC is included in the filename.
 
-#### <a name=06>Sign a transaction (offline computer):</a>
+#### <a name=06>Create a keylist file (online computer):</a>
 
-Now copy the raw transaction you've just created to a USB stick and transfer it
-to your offline computer for signing.  For this you'll need the key corresponding
-to the transaction's input address, 1F93Znz....  If the key in question is in a
-bitcoind 'wallet.dat', copy 'wallet.dat' to your offline machine as well and use
-the included command 'mmgen-pywallet' (a modified version of the well-known
-pywallet utility) to extract the required key from it.
+To sign your transaction, you'll need the Bitcoin private key corresponding to
+its input address, '1F93Znz....'
 
-		$ mmgen-pywallet -k wallet.dat
-		...
-		Private keys written to file 'wd_EDBC983A[102].keys'
+If the key in question is in a bitcoind wallet ('wallet.dat'), you'll want to
+extract the key (along with all the other keys in the wallet) to a keylist
+file.  This is done using the 'bitcoin-cli dumpwallet' command with bitcoind
+running.
+
+		$ bitcoin-cli dumpwallet my_secret.keys
+
+This will write the keylist file 'my_secret.keys' (or whatever filename you've
+chosen) to your home directory (or maybe to your Bitcoin data directory, results
+may vary).  If you want it written to another location, provide an absolute
+path.
+
+Note that the keylist file lists your private keys in *unencrypted* form, even
+if your 'wallet.dat' was encrypted.  Therefore, it should be backed up to a safe
+location—to a USB stick, say, or to your offline computer.  After you've backed
+it up, securely delete all copies of it on your online computer.
 
-You've in fact extracted a list of all of the wallet's 102 keys here, but that's
-not a problem, since the unused keys will be ignored (if you wish, you can
-extract only the one key you need using the '--keys-for-addrs' option).  Now go
-ahead and sign the transaction using this key list:
+You'll use this keylist file to sign all transactions that spend from addresses
+in your bitcoind wallet.
 
-		$ mmgen-txsign -k wd_EDBC983A[102].keys tx_FEDCBA[6.6].raw
+If the key/address pair in question came from another source, you may create
+your own file 'my_secret.keys' (or whatever) in a plain text editor and just
+list the key in this file.  In the case of multiple keys, just list them all,
+one key per line.  In our example, the file will have one line containing a
+single private key corresponding to the address '1F93Znz....'
+
+#### <a name=06>Sign a transaction (offline computer):</a>
+
+Now transfer the the raw transaction file and just-created keylist file to your
+offline computer and run:
+
+		$ mmgen-txsign -k my_secret.keys tx_FEDCBA[6.6].raw
 		...
 		Signed transaction written to file 'tx_FEDCBA[6.6].sig'
 
-Note that 'mmgen-pywallet's output is just a flat list of keys.  So if you have
-several Bitcoin wallets with balances, you can just concatenate these lists into
-a single file which you can use to sign all future transactions with
-'wallet.dat' inputs:
-
-		$ mmgen-pywallet -k wallet1.dat
-		$ mmgen-pywallet -k wallet2.dat
-		$ mmgen-pywallet -k wallet3.dat
-		$ cat wd_*.keys > all_keys
+The signed transaction is written to a new file whose name differs from the raw
+transaction file only by its '.sig' extension.
 
-Once you've migrated your funds to MMGen, such key files will no longer be
+NOTE: once you've migrated your funds to MMGen, the keylist file will no longer be
 needed.  Instead, you'll sign transactions by listing an MMGen seed source
-(wallet, mnemonic or seed file) on the command line after the transaction,
-and the required keys will be generated on the fly, as in this example:
+(wallet, mnemonic or seed file) on the command line after the transaction, and
+the required keys will be generated on the fly, as in the following example:
 
-		$ mmgen-txsign tx_ABCDE[1.2345].raw 1234567A-BCDEF123[256,3].mmdat
+		$ mmgen-txsign tx_ABCDE[1].raw my_mmgen_wallet.mmdat
 
-Transactions may contain a mixture of MMGen and non-MMGen inputs, as well as
-inputs with more than one MMGen Seed ID.  Just list a seed source for each
+NOTE: transactions may contain a mixture of MMGen and non-MMGen inputs, as well
+as inputs with more than one MMGen Seed ID.  Just list a seed source for each
 MMGen input on the command line after the transaction, as in this example:
 
-		$ mmgen-txsign -k key_list my_tx.raw a.mmdat b.mmwords c.mmseed
+		$ mmgen-txsign -k my_secret.keys my_tx.raw a.mmdat b.mmwords c.mmseed
 
 #### <a name=07>Send a transaction (online computer):</a>
 
 Now you're ready for the final step: broadcasting the transaction to the
-network.  Copy just-created signed transaction file to your online computer,
+network.  Copy the just-created signed transaction file to your online computer,
 start bitcoind and issue the command:
 
 		$ mmgen-txsend tx_FEDCBA[6.6].sig
@@ -301,7 +312,7 @@ Your total MMGen balance will also now be visible:
 		89ABCDEF:     0 BTC            0 BTC            9.99995 BTC
 		TOTAL:        0 BTC            0 BTC            9.99995 BTC
 
-To verify that your transaction's received its first, second, third and so forth
+To verify that your transaction's received its first, second, third, and so on,
 confirmation, increase the 'minconf' value to 1, 2, 3 and so forth.
 
 Congratulations!  You've performed your first MMGen transaction and placed your

+ 1 - 1
mmgen-addrgen

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-addrimport

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-keygen

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-passchg

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 0 - 24
mmgen-pywallet

@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-mmgen-pywallet: Dump contents of a bitcoind wallet to file
-"""
-
-from mmgen.main import launch
-launch("pywallet")

+ 1 - 1
mmgen-tool

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-txcreate

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-txsend

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-txsign

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-walletchk

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-walletconv

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen-walletgen

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen/__init__.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 76 - 82
mmgen/addr.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,17 +20,13 @@
 addr.py:  Address generation/display routines for the MMGen suite
 """
 
-import sys
-from hashlib import sha256, sha512
-from hashlib import new as hashlib_new
+from hashlib import sha256, sha512, new as hashlib_new
 from binascii import hexlify, unhexlify
 
+from mmgen.common import *
 from mmgen.bitcoin import numtowif
-from mmgen.util import *
 from mmgen.tx import *
 from mmgen.obj import *
-import mmgen.globalvars as g
-import mmgen.opt as opt
 
 pnm = g.proj_name
 
@@ -54,7 +50,7 @@ addrmsgs = {
 Executable '{kconv}' unavailable. Falling back on (slow) internal ECDSA library.
 Please install '{kconv}' from the {vgen} package on your system for much
 faster address generation.
-""".format(kconv=g.keyconv_exec, vgen="vanitygen")
+""".format(kconv=g.keyconv_exec, vgen='vanitygen')
 }
 
 def test_for_keyconv(silent=False):
@@ -69,19 +65,19 @@ def test_for_keyconv(silent=False):
 	return True
 
 
-def generate_addrs(seed, addrnums, source="addrgen"):
+def generate_addrs(seed, addrnums, source='addrgen'):
 
 	from util import make_chksum_8
 	seed_id = make_chksum_8(seed) # Must do this before seed gets clobbered
 
 	if 'a' in opt.gen_what:
 		if opt.no_keyconv or test_for_keyconv() == False:
-			msg("Using (slow) internal ECDSA library for address generation")
+			msg('Using (slow) internal ECDSA library for address generation')
 			from mmgen.bitcoin import privnum2addr
 			keyconv = False
 		else:
 			from subprocess import check_output
-			keyconv = "keyconv"
+			keyconv = 'keyconv'
 
 	addrnums = sorted(set(addrnums)) # don't trust the calling function
 	t_addrs,num,pos,out = len(addrnums),0,0,[]
@@ -102,7 +98,7 @@ def generate_addrs(seed, addrnums, source="addrgen"):
 
 		pos += 1
 
-		qmsg_r("\rGenerating %s #%s (%s of %s)" % (w[0],num,pos,t_addrs))
+		qmsg_r('\rGenerating %s #%s (%s of %s)' % (w[0],num,pos,t_addrs))
 
 		e = AddrInfoEntry()
 		e.idx = num
@@ -123,7 +119,7 @@ def generate_addrs(seed, addrnums, source="addrgen"):
 		out.append(e)
 
 	m = w[0] if t_addrs == 1 else w[0]+w[1]
-	qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15))
+	qmsg('\r%s: %s %s generated%s' % (seed_id,t_addrs,m,' '*15))
 	a = AddrInfo(has_keys='k' in opt.gen_what, source=source)
 	a.initialize(seed_id,out)
 	return a
@@ -131,7 +127,7 @@ def generate_addrs(seed, addrnums, source="addrgen"):
 def _parse_addrfile_body(lines,has_keys=False,check=False):
 
 	if has_keys and len(lines) % 2:
-		return "Key-address file has odd number of lines"
+		return 'Key-address file has odd number of lines'
 
 	ret = []
 	while lines:
@@ -145,7 +141,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
 			return "'%s': invalid Bitcoin address" % d[1]
 
 		if len(d) == 3: check_addr_label(d[2])
-		else:           d.append("")
+		else:           d.append('')
 
 		a.idx,a.addr,a.comment = int(d[0]),unicode(d[1]),unicode(d[2])
 
@@ -153,7 +149,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
 			l = lines.pop(0)
 			d = l.split(None,2)
 
-			if d[0] != "wif:":
+			if d[0] != 'wif:':
 				return "Invalid key line in file: '%s'" % l
 			if not is_wif(d[1]):
 				return "'%s': invalid Bitcoin key" % d[1]
@@ -162,14 +158,14 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
 
 		ret.append(a)
 
-	if has_keys and keypress_confirm("Check key-to-address validity?"):
+	if has_keys and keypress_confirm('Check key-to-address validity?'):
 		wif2addr_f = get_wif2addr_f()
 		llen = len(ret)
 		for n,e in enumerate(ret):
-			msg_r("\rVerifying keys %s/%s" % (n+1,llen))
+			msg_r('\rVerifying keys %s/%s' % (n+1,llen))
 			if e.addr != wif2addr_f(e.wif):
 				return "Key doesn't match address!\n  %s\n  %s" % (e.wif,e.addr)
-		msg(" - done")
+		msg(' - done')
 
 	return ret
 
@@ -177,7 +173,7 @@ def _parse_addrfile_body(lines,has_keys=False,check=False):
 def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 
 	if buf: lines = remove_comments(buf.splitlines()) # DOS-safe
-	else:   lines = get_lines_from_file(fn,"address data",trim_comments=True)
+	else:   lines = get_lines_from_file(fn,'address data',trim_comments=True)
 
 	try:
 		sid,obrace = lines[0].split()
@@ -196,17 +192,14 @@ def _parse_addrfile(fn,buf=[],has_keys=False,exit_on_error=True):
 			if type(ret) == list: return sid,ret
 			else: errmsg = ret
 
-	if exit_on_error:
-		msg(errmsg)
-		sys.exit(3)
-	else:
-		return False
+	if exit_on_error: die(3,errmsg)
+	else:             return False
 
 
 def _parse_keyaddr_file(fn):
 	from mmgen.crypto import mmgen_decrypt_file_maybe
-	d = mmgen_decrypt_file_maybe(fn,"key-address file")
-	return _parse_addrfile("",buf=d,has_keys=True,exit_on_error=False)
+	d = mmgen_decrypt_file_maybe(fn,'key-address file')
+	return _parse_addrfile('',buf=d,has_keys=True,exit_on_error=False)
 
 
 class AddrInfoList(MMGenObject):
@@ -225,31 +218,31 @@ class AddrInfoList(MMGenObject):
 			return self.data[sid]
 
 	def mmaddr2btcaddr(self,mmaddr):
-		btcaddr = ""
-		sid,idx = mmaddr.split(":")
+		btcaddr = ''
+		sid,idx = mmaddr.split(':')
 		if sid in self.seed_ids():
 			btcaddr = self.addrinfo(sid).btcaddr(int(idx))
 		return btcaddr
 
 	def add_wallet_data(self,c):
-		vmsg_r("Getting account data from wallet...")
-		data,accts,i = {},c.listaccounts(minconf=0,includeWatchonly=True),0
+		vmsg_r('Getting account data from wallet...')
+		accts = c.listaccounts(0,True)
+		data,i = {},0
 		for acct in accts:
 			ma,comment = parse_mmgen_label(acct)
 			if ma:
 				i += 1
 				addrlist = c.getaddressesbyaccount(acct)
 				if len(addrlist) != 1:
-					msg(wmsg['too_many_acct_addresses'] % acct)
-					sys.exit(2)
-				seed_id,idx = ma.split(":")
+					die(2,wmsg['too_many_acct_addresses'] % acct)
+				seed_id,idx = ma.split(':')
 				if seed_id not in data:
 					data[seed_id] = []
 				a = AddrInfoEntry()
 				a.idx,a.addr,a.comment = \
 					int(idx),unicode(addrlist[0]),unicode(comment)
 				data[seed_id].append(a)
-		vmsg("{n} {pnm} addresses found, {m} accounts total".format(
+		vmsg('{n} {pnm} addresses found, {m} accounts total'.format(
 				n=i,pnm=pnm,m=len(accts)))
 		for sid in data:
 			self.add(AddrInfo(sid=sid,adata=data[sid]))
@@ -259,8 +252,7 @@ class AddrInfoList(MMGenObject):
 			self.data[addrinfo.seed_id] = addrinfo
 			return True
 		else:
-			msg("Error: object %s is not of type AddrInfo" % repr(addrinfo))
-			sys.exit(1)
+			die(1,'Error: object %s is not of type AddrInfo' % repr(addrinfo))
 
 	def make_reverse_dict(self,btcaddrs):
 		d = {}
@@ -274,53 +266,57 @@ class AddrInfoEntry(MMGenObject):
 
 class AddrInfo(MMGenObject):
 
-	def __init__(self,addrfile="",has_keys=False,sid="",adata=[], source=""):
+	def __init__(self,addrfile='',has_keys=False,sid='',adata=[],source='',caller=''):
 		self.has_keys = has_keys
+		self.caller = caller
 		do_chksum = True
 		if addrfile:
-			f = _parse_keyaddr_file if has_keys else _parse_addrfile
+			f = (_parse_addrfile,_parse_keyaddr_file)[bool(has_keys)]
 			sid,adata = f(addrfile)
-			self.source = "addrfile"
+			self.source = 'addrfile'
 		elif sid and adata: # data from wallet
-			self.source = "wallet"
+			self.source = 'wallet'
 		elif sid or adata:
-			die(3,"Must specify address file, or seed_id + adata")
+			die(3,'Must specify address file, or seed_id + adata')
 		else:
-			self.source = source if source else "unknown"
+			self.source = source if source else 'unknown'
 			return
 
 		self.initialize(sid,adata)
 
 	def initialize(self,seed_id,addrdata):
 		if seed_id in self.__dict__:
-			msg("Seed ID already set for object %s" % self)
+			msg('Seed ID already set for object %s' % self)
 			return False
 		self.seed_id = seed_id
 		self.addrdata = addrdata
 		self.num_addrs = len(addrdata)
-		if self.source in ("wallet","txsign"):
+		if self.source in ('wallet','txsign'):
 			self.checksum = None
 			self.idxs_fmt = None
-		elif self.source == "addrgen" and opt.gen_what == "k":
+		elif self.source == 'addrgen' and opt.gen_what == 'k':
 			self.checksum = None
 			self.fmt_addr_idxs()
 		else: # self.source in addrfile, addrgen
 			self.make_addrdata_chksum()
-			self.fmt_addr_idxs()
-			w = "key-address" if self.has_keys else "address"
-			qmsg("Checksum for %s data %s[%s]: %s" %
-					(w,self.seed_id,self.idxs_fmt,self.checksum))
-			if self.source == "addrgen":
-				qmsg(
-"Record this checksum: it will be used to verify the address file in the future")
-			elif self.source == "addrfile":
-				qmsg("Check this value against your records")
+			if self.caller == 'tool':
+				Msg(self.checksum)
+			else:
+				self.fmt_addr_idxs()
+				w = ('address','key-address')[bool(self.has_keys)]
+				qmsg('Checksum for %s data %s[%s]: %s' %
+						(w,self.seed_id,self.idxs_fmt,self.checksum))
+				if self.source == 'addrgen':
+					qmsg(
+	'Record this checksum: it will be used to verify the address file in the future')
+				elif self.source == 'addrfile':
+					qmsg('Check this value against your records')
 
 	def idxs(self):
 		return [e.idx for e in self.addrdata]
 
 	def addrs(self):
-		return ["%s:%s"%(self.seed_id,e.idx) for e in self.addrdata]
+		return ['%s:%s'%(self.seed_id,e.idx) for e in self.addrdata]
 
 	def addrpairs(self):
 		return [(e.idx,e.addr) for e in self.addrdata]
@@ -355,15 +351,15 @@ class AddrInfo(MMGenObject):
 		d,b = {},btcaddrs
 		for e in self.addrdata:
 			try:
-				d[b[b.index(e.addr)]] = ("%s:%s"%(self.seed_id,e.idx),e.comment)
+				d[b[b.index(e.addr)]] = ('%s:%s'%(self.seed_id,e.idx),e.comment)
 			except: pass
 		return d
 
 
 	def make_addrdata_chksum(self):
-		lines=[" ".join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
+		lines=[' '.join([str(e.idx),e.addr]+([e.wif] if self.has_keys else []))
 						for e in self.addrdata]
-		self.checksum = make_chksum_N(" ".join(lines), nchars=24, sep=True)
+		self.checksum = make_chksum_N(' '.join(lines), nchars=24, sep=True)
 
 
 	def fmt_data(self,enable_comments=False):
@@ -377,53 +373,51 @@ class AddrInfo(MMGenObject):
 
 		for i,s in enumerate(status):
 			if s != 0 and s != self.num_addrs:
-				msg("%s missing %s in addr data"% (self.num_addrs-s,attrs[i]))
-				sys.exit(3)
+				die(3,'%s missing %s in addr data'% (self.num_addrs-s,attrs[i]))
 
 		if status[0] == status[1] == 0:
-			msg("Addr data contains neither addresses nor keys")
-			sys.exit(3)
+			die(3,'Addr data contains neither addresses nor keys')
 
 		# Header
 		out = []
 		from mmgen.addr import addrmsgs
-		k = ('addrfile_header','keyfile_header')[int(status[0]==0)]
-		out.append(addrmsgs[k]+"\n")
+		k = ('addrfile_header','keyfile_header')[status[0]==0]
+		out.append(addrmsgs[k]+'\n')
 		if self.checksum:
-			w = ("Key-address","Address")[int(status[1]==0)]
-			out.append("# {} data checksum for {}[{}]: {}".format(
+			w = ('Key-address','Address')[status[1]==0]
+			out.append('# {} data checksum for {}[{}]: {}'.format(
 						w, self.seed_id, self.idxs_fmt, self.checksum))
-			out.append("# Record this value to a secure location.\n")
-		out.append("%s {" % self.seed_id)
+			out.append('# Record this value to a secure location.\n')
+		out.append('%s {' % self.seed_id)
 
 		# Body
-		fs = "  {:<%s}  {:<34}{}" % len(str(self.addrdata[-1].idx))
+		fs = '  {:<%s}  {:<34}{}' % len(str(self.addrdata[-1].idx))
 		for e in self.addrdata:
-			c = ""
+			c = ''
 			if enable_comments:
-				try:    c = " "+e.comment
+				try:    c = ' '+e.comment
 				except: pass
 			if status[0]:  # First line with idx
 				out.append(fs.format(e.idx, e.addr,c))
 			else:
-				out.append(fs.format(e.idx, "wif: "+e.wif,c))
+				out.append(fs.format(e.idx, 'wif: '+e.wif,c))
 
 			if status[1]:   # Subsequent lines
 				if status[2]:
-					out.append(fs.format("", "hex: "+e.sec,c))
+					out.append(fs.format('', 'hex: '+e.sec,c))
 				if status[0]:
-					out.append(fs.format("", "wif: "+e.wif,c))
+					out.append(fs.format('', 'wif: '+e.wif,c))
 
-		out.append("}")
+		out.append('}')
 
-		return "\n".join([l.rstrip() for l in out]) + "\n"
+		return '\n'.join([l.rstrip() for l in out]) + '\n'
 
 
 	def fmt_addr_idxs(self):
 
 		try: int(self.addrdata[0].idx)
 		except:
-			self.idxs_fmt = "(no idxs)"
+			self.idxs_fmt = '(no idxs)'
 			return
 
 		addr_idxs = [e.idx for e in self.addrdata]
@@ -432,10 +426,10 @@ class AddrInfo(MMGenObject):
 
 		for i in addr_idxs[1:]:
 			if i == prev + 1:
-				if i == addr_idxs[-1]: ret += "-", i
+				if i == addr_idxs[-1]: ret += '-', i
 			else:
-				if prev != ret[-1]: ret += "-", prev
-				ret += ",", i
+				if prev != ret[-1]: ret += '-', prev
+				ret += ',', i
 			prev = i
 
-		self.idxs_fmt = "".join([str(i) for i in ret])
+		self.idxs_fmt = ''.join([str(i) for i in ret])

+ 21 - 21
mmgen/bitcoin.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -37,7 +37,7 @@ _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
 curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b )
 generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
 oid_secp256k1 = (1,3,132,0,10)
-secp256k1 = ecdsa.curves.Curve("secp256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1)
+secp256k1 = ecdsa.curves.Curve('secp256k1', curve_secp256k1, generator_secp256k1, oid_secp256k1)
 
 b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 
@@ -49,8 +49,8 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 #
 # Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk
 #
-# The "zero address":
-# 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate)
+# The 'zero address':
+# 1111111111111111111114oLvT2 (use step2 = ('0' * 40) to generate)
 #
 
 def pubhex2hexaddr(pubhex):
@@ -63,8 +63,8 @@ def hexaddr2addr(hexaddr, vers_num='00'):
 	step1 = sha256(unhexlify(hexaddr2)).digest()
 	step2 = sha256(step1).hexdigest()
 	pubkey = hexaddr2 + step2[:8]
-	lzeroes = (len(hexaddr2) - len(hexaddr2.lstrip("0"))) / 2
-	return ("1" * lzeroes) + _numtob58(int(pubkey,16))
+	lzeroes = (len(hexaddr2) - len(hexaddr2.lstrip('0'))) / 2
+	return ('1' * lzeroes) + _numtob58(int(pubkey,16))
 
 def verify_addr(addr,verbose=False,return_hex=False):
 
@@ -72,7 +72,7 @@ def verify_addr(addr,verbose=False,return_hex=False):
 		if addr[0] != ldigit: continue
 		num = _b58tonum(addr)
 		if num == False: break
-		addr_hex = "{:050x}".format(num)
+		addr_hex = '{:050x}'.format(num)
 		if addr_hex[:2] != vers_num: continue
 		step1 = sha256(unhexlify(addr_hex[:42])).digest()
 		step2 = sha256(step1).hexdigest()
@@ -101,7 +101,7 @@ def _b58tonum(b58num):
 	return sum([b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))])
 
 def numtowif(numpriv):
-	step1 = '80' + "{:064x}".format(numpriv)
+	step1 = '80' + '{:064x}'.format(numpriv)
 	step2 = sha256(unhexlify(step1)).digest()
 	step3 = sha256(step2).hexdigest()
 	key = step1 + step3[:8]
@@ -113,17 +113,17 @@ def numtowif(numpriv):
 # (well, not exactly: they yield numeric but not bytewise equivalence)
 
 def b58encode(s):
-	if s == "": return ""
+	if s == '': return ''
 	num = int(hexlify(s),16)
 	return _numtob58(num)
 
 def b58decode(b58num):
-	if b58num == "": return ""
+	if b58num == '': return ''
 	# Zap all spaces:
 	num = _b58tonum(b58num.translate(None,' \t\n\r'))
 	if num == False: return False
-	out = "{:x}".format(num)
-	return unhexlify("0"*(len(out)%2) + out)
+	out = '{:x}'.format(num)
+	return unhexlify('0'*(len(out)%2) + out)
 
 # These yield bytewise equivalence in our special cases:
 
@@ -134,36 +134,36 @@ def _b58_pad(s,a,b,pad,f,w):
 	try:
 		outlen = b[a.index(len(s))]
 	except:
-		Msg("_b58_pad() accepts only %s %s bytes long "\
-			"(input was %s bytes)" % (w,",".join([str(i) for i in a]),len(s)))
+		Msg('_b58_pad() accepts only %s %s bytes long '\
+			'(input was %s bytes)' % (w,','.join([str(i) for i in a]),len(s)))
 		return False
 
 	out = f(s)
 	if out == False: return False
-	return "%s%s" % (pad * (outlen - len(out)), out)
+	return '%s%s' % (pad * (outlen - len(out)), out)
 
 def b58encode_pad(s):
 	return _b58_pad(s,
-		a=bin_lens,b=b58_lens,pad="1",f=b58encode,w="binary strings")
+		a=bin_lens,b=b58_lens,pad='1',f=b58encode,w='binary strings')
 
 def b58decode_pad(s):
 	return _b58_pad(s,
-		a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers")
+		a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w='base 58 numbers')
 
 # Compressed address support:
 
 def wiftohex(wifpriv,compressed=False):
-	idx = 68 if compressed else 66
+	idx = (66,68)[bool(compressed)]
 	num = _b58tonum(wifpriv)
 	if num == False: return False
-	key = "{:x}".format(num)
+	key = '{:x}'.format(num)
 	if compressed and key[66:68] != '01': return False
 	round1 = sha256(unhexlify(key[:idx])).digest()
 	round2 = sha256(round1).hexdigest()
 	return key[2:66] if (key[:2] == '80' and key[idx:] == round2[:8]) else False
 
 def hextowif(hexpriv,compressed=False):
-	step1 = '80' + hexpriv + ('01' if compressed else '')
+	step1 = '80' + hexpriv + ('','01')[bool(compressed)]
 	step2 = sha256(unhexlify(step1)).digest()
 	step3 = sha256(step2).hexdigest()
 	key = step1 + step3[:8]
@@ -173,7 +173,7 @@ def privnum2pubhex(numpriv,compressed=False):
 	pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
 	pubkey = hexlify(pko.get_verifying_key().to_string())
 	if compressed:
-		p = '02' if pubkey[-1] in "02468ace" else '03'
+		p = ('03','02')[pubkey[-1] in '02468ace']
 		return p+pubkey[:64]
 	else:
 		return '04'+pubkey

+ 8 - 3
mmgen/opt.py → mmgen/common.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,6 +17,11 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-opt.py: a namespace for the global opt variables
+common.py:  Common imports for all MMGen scripts
 """
-import opts
+
+import sys
+import mmgen.globalvars as g
+import mmgen.opts as opts
+from mmgen.opts import opt
+from mmgen.util import *

+ 59 - 63
mmgen/crypto.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,13 +20,10 @@
 crypto.py:  Cryptographic and related routines for the MMGen suite
 """
 
-import sys
 from binascii import hexlify
 from hashlib import sha256
 
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import *
+from mmgen.common import *
 from mmgen.term import get_char
 
 crmsg = {
@@ -58,73 +55,72 @@ keystrokes will also be used as a source of randomness.
 }
 
 def encrypt_seed(seed, key):
-	return encrypt_data(seed, key, iv=1, desc="seed")
+	return encrypt_data(seed, key, iv=1, desc='seed')
 
 
 def decrypt_seed(enc_seed, key, seed_id, key_id):
 
-	vmsg_r("Checking key...")
+	vmsg_r('Checking key...')
 	chk1 = make_chksum_8(key)
 	if key_id:
-		if not compare_chksums(key_id,"key ID",chk1,"computed"):
-			msg("Incorrect passphrase or hash preset")
+		if not compare_chksums(key_id,'key ID',chk1,'computed'):
+			msg('Incorrect passphrase or hash preset')
 			return False
 
-	dec_seed = decrypt_data(enc_seed, key, iv=1, desc="seed")
+	dec_seed = decrypt_data(enc_seed, key, iv=1, desc='seed')
 
 	chk2 = make_chksum_8(dec_seed)
 
 	if seed_id:
-		if compare_chksums(seed_id,"Seed ID",chk2,"decrypted seed"):
-			qmsg("Passphrase is OK")
+		if compare_chksums(seed_id,'Seed ID',chk2,'decrypted seed'):
+			qmsg('Passphrase is OK')
 		else:
 			if not opt.debug:
-				msg_r("Checking key ID...")
-				if compare_chksums(key_id,"key ID",chk1,"computed"):
-					msg("Key ID is correct but decryption of seed failed")
+				msg_r('Checking key ID...')
+				if compare_chksums(key_id,'key ID',chk1,'computed'):
+					msg('Key ID is correct but decryption of seed failed')
 				else:
-					msg("Incorrect passphrase or hash preset")
+					msg('Incorrect passphrase or hash preset')
 
-			vmsg("")
+			vmsg('')
 			return False
 #	else:
-#		qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
+#		qmsg('Generated IDs (Seed/Key): %s/%s' % (chk2,chk1))
 
-	dmsg("Decrypted seed: %s" % hexlify(dec_seed))
+	dmsg('Decrypted seed: %s' % hexlify(dec_seed))
 
 	return dec_seed
 
 
-def encrypt_data(data, key, iv=1, desc="data", verify=True):
+def encrypt_data(data, key, iv=1, desc='data', verify=True):
 
 	# 192-bit seed is 24 bytes -> not multiple of 16.  Must use MODE_CTR
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
 
-	vmsg("Encrypting %s" % desc)
+	vmsg('Encrypting %s' % desc)
 
 	c = AES.new(key, AES.MODE_CTR,
 			counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 	enc_data = c.encrypt(data)
 
 	if verify:
-		vmsg_r("Performing a test decryption of the %s..." % desc)
+		vmsg_r('Performing a test decryption of the %s...' % desc)
 
 		c = AES.new(key, AES.MODE_CTR,
 				counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
 		dec_data = c.decrypt(enc_data)
 
-		if dec_data == data: vmsg("done")
+		if dec_data == data: vmsg('done')
 		else:
-			msg("ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc))
-			sys.exit(2)
+			die(2,"ERROR.\nDecrypted %s doesn't match original %s" % (desc,desc))
 
 	return enc_data
 
 
-def decrypt_data(enc_data, key, iv=1, desc="data"):
+def decrypt_data(enc_data, key, iv=1, desc='data'):
 
-	vmsg_r("Decrypting %s with key..." % desc)
+	vmsg_r('Decrypting %s with key...' % desc)
 
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
@@ -147,66 +143,66 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
 
 
 def make_key(passwd,salt,hash_preset,
-		desc="encryption key",from_what="passphrase",verbose=False):
+		desc='encryption key',from_what='passphrase',verbose=False):
 
-	if from_what: desc += " from "
+	if from_what: desc += ' from '
 	if opt.verbose or verbose:
-		msg_r("Generating %s%s..." % (desc,from_what))
+		msg_r('Generating %s%s...' % (desc,from_what))
 	key = scrypt_hash_passphrase(passwd, salt, hash_preset)
-	if opt.verbose or verbose: msg("done")
-	dmsg("Key: %s" % hexlify(key))
+	if opt.verbose or verbose: msg('done')
+	dmsg('Key: %s' % hexlify(key))
 	return key
 
 
 def _get_random_data_from_user(uchars):
 
-	if opt.quiet: msg("Enter %s random symbols" % uchars)
+	if opt.quiet: msg('Enter %s random symbols' % uchars)
 	else:       msg(crmsg['usr_rand_notice'] % uchars)
 
-	prompt = "You may begin typing.  %s symbols left: "
+	prompt = 'You may begin typing.  %s symbols left: '
 	msg_r(prompt % uchars)
 
 	import time
 	# time.clock() always returns zero, so we'll use time.time()
 	saved_time = time.time()
 
-	key_data,time_data,pp = "",[],True
+	key_data,time_data,pp = '',[],True
 
 	for i in range(uchars):
-		key_data += get_char(immed_chars="ALL",prehold_protect=pp)
+		key_data += get_char(immed_chars='ALL',prehold_protect=pp)
 		pp = False
-		msg_r("\r" + prompt % (uchars - i - 1))
+		msg_r('\r' + prompt % (uchars - i - 1))
 		now = time.time()
 		time_data.append(now - saved_time)
 		saved_time = now
 
-	if opt.quiet: msg_r("\r")
-	else: msg_r("\rThank you.  That's enough.%s\n\n" % (" "*18))
+	if opt.quiet: msg_r('\r')
+	else: msg_r("\rThank you.  That's enough.%s\n\n" % (' '*18))
 
-	fmt_time_data = ["{:.22f}".format(i) for i in time_data]
+	fmt_time_data = ['{:.22f}'.format(i) for i in time_data]
 
-	dmsg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
-				(key_data,"\n".join(fmt_time_data)))
+	dmsg('\nUser input:\n%s\nKeystroke time intervals:\n%s\n' %
+				(key_data,'\n'.join(fmt_time_data)))
 
-	prompt = "User random data successfully acquired.  Press ENTER to continue"
-	prompt_and_get_char(prompt,"",enter_ok=True)
+	prompt = 'User random data successfully acquired.  Press ENTER to continue'
+	prompt_and_get_char(prompt,'',enter_ok=True)
 
-	return key_data+"".join(fmt_time_data)
+	return key_data+''.join(fmt_time_data)
 
 
 def get_random(length):
 	from Crypto import Random
 	os_rand = Random.new().read(length)
 	if g.use_urandchars and opt.usr_randchars:
-		from_what = "OS random data"
+		from_what = 'OS random data'
 		if not g.user_entropy:
 			g.user_entropy = \
 				sha256(_get_random_data_from_user(opt.usr_randchars)).digest()
-			from_what += " plus user-supplied entropy"
+			from_what += ' plus user-supplied entropy'
 		else:
-			from_what += " plus saved user-supplied entropy"
-		key = make_key(g.user_entropy, "", '2', from_what=from_what, verbose=True)
-		return encrypt_data(os_rand,key,desc="random data",verify=False)
+			from_what += ' plus saved user-supplied entropy'
+		key = make_key(g.user_entropy, '', '2', from_what=from_what, verbose=True)
+		return encrypt_data(os_rand,key,desc='random data',verify=False)
 	else:
 		return os_rand
 
@@ -214,13 +210,13 @@ def get_random(length):
 # Vars for mmgen_*crypt functions only
 salt_len,sha256_len,nonce_len = 32,32,32
 
-def mmgen_encrypt(data,desc="data",hash_preset=''):
+def mmgen_encrypt(data,desc='data',hash_preset=''):
 	salt,iv,nonce = get_random(salt_len),\
 					get_random(g.aesctr_iv_len), \
 					get_random(nonce_len)
 	hp = hash_preset or get_hash_preset_from_user('3',desc)
-	m = "default" if hp == '3' else "user-requested"
-	vmsg("Encrypting %s" % desc)
+	m = ('user-requested','default')[hp=='3']
+	vmsg('Encrypting %s' % desc)
 	qmsg("Using %s hash preset of '%s'" % (m,hp))
 	passwd = get_new_passphrase(desc, {})
 	key = make_key(passwd, salt, hp)
@@ -229,34 +225,34 @@ def mmgen_encrypt(data,desc="data",hash_preset=''):
 	return salt+iv+enc_d
 
 
-def mmgen_decrypt(data,desc="data",hash_preset=""):
+def mmgen_decrypt(data,desc='data',hash_preset=''):
 	dstart = salt_len + g.aesctr_iv_len
 	salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
-	vmsg("Preparing to decrypt %s" % desc)
+	vmsg('Preparing to decrypt %s' % desc)
 	hp = hash_preset or get_hash_preset_from_user('3',desc)
-	m = "default" if hp == '3' else "user-requested"
+	m = ('user-requested','default')[hp=='3']
 	qmsg("Using %s hash preset of '%s'" % (m,hp))
 	passwd = get_mmgen_passphrase(desc)
 	key = make_key(passwd, salt, hp)
 	dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), desc)
 	if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
-		vmsg("OK")
+		vmsg('OK')
 		return dec_d[sha256_len+nonce_len:]
 	else:
-		msg("Incorrect passphrase or hash preset")
+		msg('Incorrect passphrase or hash preset')
 		return False
 
 def mmgen_decrypt_file_maybe(fn,desc):
-	d = get_data_from_file(fn,"{} data".format(desc),binary=True)
+	d = get_data_from_file(fn,'{} data'.format(desc),binary=True)
 	have_enc_ext = get_extension(fn) == g.mmenc_ext
 	if have_enc_ext or not is_ascii(d):
-		m = ("Attempting to decrypt","Decrypting")[int(have_enc_ext)]
-		msg("%s %s %s" % (m,desc,fn))
+		m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
+		msg('%s %s %s' % (m,desc,fn))
 		d = mmgen_decrypt_retry(d,desc)
 	return d
 
-def mmgen_decrypt_retry(d,desc="data"):
+def mmgen_decrypt_retry(d,desc='data'):
 	while True:
 		d_dec = mmgen_decrypt(d,desc)
 		if d_dec: return d_dec
-		msg("Trying again...")
+		msg('Trying again...')

+ 2 - 2
mmgen/filename.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -41,7 +41,7 @@ class Filename(MMGenObject):
 				die(2,"Unrecognized extension '.%s' for file '%s'" % (self.ext,fn))
 
 		# TODO: Check for Windows
-		mode = (os.O_RDONLY,os.O_RDWR)[int(write)]
+		mode = (os.O_RDONLY,os.O_RDWR)[bool(write)]
 		import stat
 		if stat.S_ISBLK(os.stat(fn).st_mode):
 			try:

+ 39 - 39
mmgen/globalvars.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,82 +17,82 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-config.py:  Constants and configuration options for the MMGen suite
+globalvars.py:  Constants and configuration options for the MMGen suite
 """
 
 import sys, os
 
 # Variables - these might be altered at runtime:
 
-user_entropy   = ""
+user_entropy   = ''
 hash_preset    = '3'
 usr_randchars  = 30
 use_urandchars = False
 
 # returns None if env var 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")
+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')
 
 from decimal import Decimal
-tx_fee        = Decimal("0.00005")
-max_tx_fee    = Decimal("0.01")
+tx_fee        = Decimal('0.00005')
+max_tx_fee    = Decimal('0.01')
 
 seed_len     = 256
-http_timeout = 30
+http_timeout = 60
 
 # Constants - these don't change at runtime
 
-proj_name = "MMGen"
+proj_name = 'MMGen'
 prog_name = os.path.basename(sys.argv[0])
-author    = "Philemon"
-email     = "<mmgen-py@yandex.com>"
-Cdates    = '2013-2015'
-version   = '0.8.2'
+author    = 'Philemon'
+email     = '<mmgen-py@yandex.com>'
+Cdates    = '2013-2016'
+version   = '0.8.3'
 
 required_opts = [
-	"quiet","verbose","debug","outdir","echo_passphrase","passwd_file",
-	"usr_randchars","stdout","show_hash_presets","label",
-	"keep_passphrase","keep_hash_preset"
+	'quiet','verbose','debug','outdir','echo_passphrase','passwd_file',
+	'usr_randchars','stdout','show_hash_presets','label',
+	'keep_passphrase','keep_hash_preset'
 ]
 incompatible_opts = (
-	("quiet","verbose"),
-	("label","keep_label"),
-	("tx_id", "info"),
-	("tx_id", "terse_info"),
+	('quiet','verbose'),
+	('label','keep_label'),
+	('tx_id', 'info'),
+	('tx_id', 'terse_info'),
 )
 min_screen_width = 80
 
-wallet_ext    = "mmdat"
-seed_ext      = "mmseed"
-mn_ext        = "mmwords"
-brain_ext     = "mmbrain"
-incog_ext     = "mmincog"
-incog_hex_ext = "mmincox"
+wallet_ext    = 'mmdat'
+seed_ext      = 'mmseed'
+mn_ext        = 'mmwords'
+brain_ext     = 'mmbrain'
+incog_ext     = 'mmincog'
+incog_hex_ext = 'mmincox'
 
 seedfile_exts = (
 	wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext, incog_hex_ext
 )
 
-rawtx_ext           = "raw"
-sigtx_ext           = "sig"
-addrfile_ext        = "addrs"
-addrfile_chksum_ext = "chk"
-keyfile_ext         = "keys"
-keyaddrfile_ext     = "akeys"
-mmenc_ext           = "mmenc"
+rawtx_ext           = 'raw'
+sigtx_ext           = 'sig'
+addrfile_ext        = 'addrs'
+addrfile_chksum_ext = 'chk'
+keyfile_ext         = 'keys'
+keyaddrfile_ext     = 'akeys'
+mmenc_ext           = 'mmenc'
 
-default_wordlist    = "electrum"
-#default_wordlist    = "tirosh"
+default_wordlist    = 'electrum'
+#default_wordlist    = 'tirosh'
 
 # Global value sets user opt
-dfl_vars = "seed_len","hash_preset","usr_randchars","debug"
+dfl_vars = 'seed_len','hash_preset','usr_randchars','debug'
 
 seed_lens = 128,192,256
 mn_lens = [i / 32 * 3 for i in seed_lens]
 
-keyconv_exec = "keyconv"
+keyconv_exec = 'keyconv'
 
 mins_per_block   = 9
 passwd_max_tries = 5

+ 2 - 2
mmgen/license.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """
-license.py:  Text of GPLv3
+license.py:  Copyright notice and text of GPLv3
 """
 
 import mmgen.globalvars as g

+ 8 - 8
mmgen/main.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,15 +22,15 @@ main.py - Script launcher for the MMGen suite
 
 def launch(what):
 
-	if what in ("walletgen","walletchk","walletconv","passchg"):
-		what = "wallet"
-	if what == "keygen": what = "addrgen"
+	if what in ('walletgen','walletchk','walletconv','passchg'):
+		what = 'wallet'
+	if what == 'keygen': what = 'addrgen'
 
 	try: import termios
 	except: # Windows
 		from mmgen.util import start_mscolor
 		start_mscolor()
-		__import__("mmgen.main_" + what)
+		__import__('mmgen.main_' + what)
 	else:
 		import sys,atexit
 		fd = sys.stdin.fileno()
@@ -38,8 +38,8 @@ def launch(what):
 		def at_exit():
 			termios.tcsetattr(fd, termios.TCSADRAIN, old)
 		atexit.register(at_exit)
-		try: __import__("mmgen.main_" + what)
+		try: __import__('mmgen.main_' + what)
 		except KeyboardInterrupt:
-			sys.stderr.write("\nUser interrupt\n")
+			sys.stderr.write('\nUser interrupt\n')
 		except EOFError:
-			sys.stderr.write("\nEnd of file\n")
+			sys.stderr.write('\nEnd of file\n')

+ 25 - 30
mmgen/main_addrgen.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,24 +21,20 @@ mmgen-addrgen: Generate a series or range of addresses from an MMGen
                deterministic wallet
 """
 
-import sys
-
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import *
+from mmgen.common import *
 from mmgen.crypto import *
 from mmgen.addr import *
 from mmgen.seed import SeedSource
 
-if sys.argv[0].split("-")[-1] == "keygen":
-	gen_what = "keys"
+if sys.argv[0].split('-')[-1] == 'keygen':
+	gen_what = 'keys'
 	opt_filter = None
 	note1 = """
 By default, both addresses and secret keys are generated.
 """.strip()
 else:
-	gen_what = "addresses"
-	opt_filter = "hcdeiHOKlpzPqSv"
+	gen_what = 'addresses'
+	opt_filter = 'hcdeiHOKlpzPqSv'
 	note1 = """
 If available, the external 'keyconv' program will be used for address
 generation.
@@ -48,7 +44,7 @@ opts_data = {
 	'sets': [('print_checksum',True,'quiet',True)],
 	'desc': """Generate a range or list of {what} from an {pnm} wallet,
                   mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
-	'usage':"[opts] [infile] <address range or list>",
+	'usage':'[opts] [infile] <address range or list>',
 	'options': """
 -h, --help            Print this help message.
 -A, --no-addresses    Print only secret keys, no addresses.
@@ -73,7 +69,7 @@ opts_data = {
 -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]),
+	seed_lens=', '.join([str(i) for i in g.seed_lens]),
 	pnm=g.proj_name,
 	what=gen_what,g=g
 ),
@@ -92,21 +88,20 @@ FMT CODES:
   {f}
 """.format(
 		n=note1,
-		f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
-		o=opt.opts
+		f='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
+		o=opts
 	)
 }
 
-
-cmd_args = opt.opts.init(opts_data,add_opts=["b16"],opt_filter=opt_filter)
+cmd_args = opts.init(opts_data,add_opts=['b16'],opt_filter=opt_filter)
 
 nargs = 2
 if len(cmd_args) < nargs \
 		and not opt.hidden_incog_input_params and not opt.in_fmt:
-	opt.opts.usage()
+	opts.usage()
 elif len(cmd_args) > nargs \
 		or (len(cmd_args) == nargs and opt.hidden_incog_input_params):
-			opt.opts.usage()
+			opts.usage()
 
 addrlist_arg = cmd_args.pop()
 addr_idxs = parse_addr_idxs(addrlist_arg)
@@ -115,8 +110,8 @@ if not addr_idxs:
 
 do_license_msg()
 
-opt.gen_what = "a" if gen_what == "addresses" \
-					else "k" if opt.no_addresses else "ka"
+opt.gen_what = 'a' if gen_what == 'addresses' \
+					else 'k' if opt.no_addresses else 'ka'
 
 # Generate data:
 ss = SeedSource(*cmd_args)
@@ -124,19 +119,19 @@ ss = SeedSource(*cmd_args)
 ainfo = generate_addrs(ss.seed.data,addr_idxs)
 
 addrdata_str = ainfo.fmt_data()
-outfile_base = "{}[{}]".format(ss.seed.sid, ainfo.idxs_fmt)
+outfile_base = '{}[{}]'.format(ss.seed.sid, ainfo.idxs_fmt)
 
 if 'a' in opt.gen_what and opt.print_checksum:
 	Die(0,ainfo.checksum)
 
-if 'k' in opt.gen_what and keypress_confirm("Encrypt key list?"):
-	addrdata_str = mmgen_encrypt(addrdata_str,"new key list","")
-	enc_ext = "." + g.mmenc_ext
-else: enc_ext = ""
+if 'k' in opt.gen_what and keypress_confirm('Encrypt key list?'):
+	addrdata_str = mmgen_encrypt(addrdata_str,'new key list','')
+	enc_ext = '.' + g.mmenc_ext
+else: enc_ext = ''
 
-ext = (g.keyfile_ext,g.keyaddrfile_ext)[int("ka" in opt.gen_what)]
-ext = (g.addrfile_ext,ext)[int("k" in opt.gen_what)]
-outfile = "%s.%s%s" % (outfile_base, ext, enc_ext)
-ask_tty = "k" in opt.gen_what and not opt.quiet
-if gen_what == "keys": gen_what = "secret keys"
+ext = (g.keyfile_ext,g.keyaddrfile_ext)['ka' in opt.gen_what]
+ext = (g.addrfile_ext,ext)['k' in opt.gen_what]
+outfile = '%s.%s%s' % (outfile_base, ext, enc_ext)
+ask_tty = 'k' in opt.gen_what and not opt.quiet
+if gen_what == 'keys': gen_what = 'secret keys'
 write_data_to_file(outfile,addrdata_str,gen_what,ask_tty=ask_tty)

+ 43 - 38
mmgen/main_addrimport.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,19 +20,19 @@
 mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet
 """
 
-import sys, time
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import *
+import time
+
+from mmgen.common import *
 from mmgen.tx import connect_to_bitcoind
 from mmgen.addr import AddrInfo,AddrInfoEntry
 
 opts_data = {
 	'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind
                      tracking wallet""".format(pnm=g.proj_name),
-	'usage':"[opts] [mmgen address file]",
+	'usage':'[opts] [mmgen address file]',
 	'options': """
 -h, --help         Print this help message
+-b, --batch        Batch mode.  Import all addresses in one RPC call
 -l, --addrlist     Address source is a flat list of addresses
 -k, --keyaddr-file Address source is a key-address file
 -q, --quiet        Suppress warnings
@@ -46,14 +46,14 @@ in the tracking wallet.
 """
 }
 
-cmd_args = opt.opts.init(opts_data)
+cmd_args = opts.init(opts_data)
 
 if len(cmd_args) == 1:
 	infile = cmd_args[0]
 	check_infile(infile)
 	if opt.addrlist:
 		lines = get_lines_from_file(
-			infile,"non-{pnm} addresses".format(pnm=g.proj_name),trim_comments=True)
+			infile,'non-{pnm} addresses'.format(pnm=g.proj_name),trim_comments=True)
 		ai,adata = AddrInfo(),[]
 		for btcaddr in lines:
 			a = AddrInfoEntry()
@@ -63,23 +63,19 @@ if len(cmd_args) == 1:
 	else:
 		ai = AddrInfo(infile,has_keys=opt.keyaddr_file)
 else:
-	msg("""
+	die(1,"""
 You must specify an {pnm} address file (or a list of non-{pnm} addresses
 with the '--addrlist' option)
 """.strip().format(pnm=g.proj_name))
-	sys.exit(1)
 
 from mmgen.bitcoin import verify_addr
-qmsg_r("Validating addresses...")
+qmsg_r('Validating addresses...')
 for e in ai.addrdata:
 	if not verify_addr(e.addr,verbose=True):
-		msg("%s: invalid address" % e.addr)
-		sys.exit(2)
-
-m = (" from Seed ID %s" % ai.seed_id) if ai.seed_id else ""
-qmsg("OK. %s addresses%s" % (ai.num_addrs,m))
+		die(2,'%s: invalid address' % e.addr)
 
-g.http_timeout = 3600
+m = (' from Seed ID %s' % ai.seed_id) if ai.seed_id else ''
+qmsg('OK. %s addresses%s' % (ai.num_addrs,m))
 
 if not opt.test:
 	c = connect_to_bitcoind()
@@ -97,35 +93,40 @@ program now and rerun it using the '--rescan' option.  Otherwise you may ignore
 this message and continue.
 """.strip()
 
-if not opt.quiet: confirm_or_exit(m, "continue", expect="YES")
+if not opt.quiet: confirm_or_exit(m, 'continue', expect='YES')
 
 err_flag = False
 
 def import_address(addr,label,rescan):
 	try:
 		if not opt.test:
-			c.importaddress(addr,label,rescan)
+			c.importaddress(addr,label,rescan,timeout=(False,3600)[rescan])
 	except:
 		global err_flag
 		err_flag = True
 
 w_n_of_m = len(str(ai.num_addrs)) * 2 + 2
-w_mmid   = "" if opt.addrlist else len(str(max(ai.idxs()))) + 12
+w_mmid   = '' if opt.addrlist else len(str(max(ai.idxs()))) + 12
 
 if opt.rescan:
 	import threading
-	msg_fmt = "\r%s %-{}s %-34s %s".format(w_n_of_m)
+	msg_fmt = '\r%s %-{}s %-34s %s'.format(w_n_of_m)
 else:
-	msg_fmt = "\r%-{}s %-34s %s".format(w_n_of_m, w_mmid)
+	msg_fmt = '\r%-{}s %-34s %s'.format(w_n_of_m, w_mmid)
+
+msg("Importing %s addresses from '%s'%s" %
+		(len(ai.addrdata),infile,('',' (batch mode)')[bool(opt.batch)]))
 
-msg("Importing addresses")
+arg_list = []
 for n,e in enumerate(ai.addrdata):
 	if e.idx:
-		label = "%s:%s" % (ai.seed_id,e.idx)
-		if e.comment: label += " " + e.comment
-	else: label = "non-{pnm}".format(pnm=g.proj_name)
+		label = '%s:%s' % (ai.seed_id,e.idx)
+		if e.comment: label += ' ' + e.comment
+	else: label = 'non-{pnm}'.format(pnm=g.proj_name)
 
-	if opt.rescan:
+	if opt.batch:
+		arg_list.append((e.addr,label,False))
+	elif opt.rescan:
 		t = threading.Thread(target=import_address, args=(e.addr,label,True))
 		t.daemon = True
 		t.start()
@@ -135,18 +136,22 @@ for n,e in enumerate(ai.addrdata):
 		while True:
 			if t.is_alive():
 				elapsed = int(time.time() - start)
-				count = "%s/%s:" % (n+1, ai.num_addrs)
-				msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,"(%s)"%label))
+				count = '%s/%s:' % (n+1, ai.num_addrs)
+				msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,'(%s)'%label))
 				time.sleep(1)
 			else:
-				if err_flag: msg("\nImport failed"); sys.exit(2)
-				msg("\nOK")
+				if err_flag: die(2,'\nImport failed')
+				msg('\nOK')
 				break
 	else:
-		import_address(e.addr,label,rescan=False)
-		count = "%s/%s:" % (n+1, ai.num_addrs)
-		msg_r(msg_fmt % (count, e.addr, "(%s)"%label))
-		if err_flag:
-			msg("\nImport failed")
-			sys.exit(2)
-		msg(" - OK")
+		import_address(e.addr,label,False)
+		count = '%s/%s:' % (n+1, ai.num_addrs)
+		msg_r(msg_fmt % (count, e.addr, '(%s)'%label))
+		if err_flag: die(2,'\nImport failed')
+		msg(' - OK')
+
+if opt.batch:
+	if opt.rescan:
+		msg('Warning: this command may take a long time to complete!')
+	ret = c.importaddress(arg_list,batch=True,timeout=(False,3600)[bool(opt.rescan)])
+	msg('OK: %s addresses imported' % len(ret))

+ 0 - 1684
mmgen/main_pywallet.py

@@ -1,1684 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-mmgen-pywallet: Dump contents of a bitcoind wallet to file
-"""
-
-# Changes by Philemon:
-#   password entry at prompt
-#   dump keys, addresses or keys for specified addresses (output in flat list)
-
-# PyWallet 1.2.1 (Public Domain)
-# http://github.com/joric/pywallet
-# Most of the actual PyWallet code placed in the public domain.
-# PyWallet includes portions of free software, listed below.
-
-# BitcoinTools (wallet.dat handling code, MIT License)
-# https://github.com/gavinandresen/bitcointools
-# Copyright (c) 2010 Gavin Andresen
-
-# python-ecdsa (EC_KEY implementation, MIT License)
-# http://github.com/warner/python-ecdsa
-# "python-ecdsa" Copyright (c) 2010 Brian Warner
-# Portions written in 2005 by Peter Pearson and placed in the public domain.
-
-# SlowAES (aes.py code, Apache 2 License)
-# http://code.google.com/p/slowaes/
-# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org),
-# Alex Martelli (http://www.aleax.it)
-# Ported from C code written by Laurent Haan (http://www.progressive-coding.com)
-
-from bsddb.db import *
-import sys, time
-import json
-import logging
-import struct
-import StringIO
-import traceback
-import socket
-import types
-import string
-import exceptions
-import hashlib
-import random
-import math
-
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import msg
-
-max_version = 60000
-addrtype = 0
-json_db = {}
-private_keys = []
-password = None
-
-opts_data = {
-	'desc':    "Dump contents of a bitcoind wallet to file",
-	'usage':   "[opts] <bitcoind wallet file>",
-	'options': """
--h, --help             Print this help message.
--d, --outdir=       d  Specify an alternate directory 'd' for output.
--e, --echo-passphrase  Display passphrase on screen upon entry.
--j, --json             Dump wallet in json format.
--k, --keys             Dump all private keys (flat list).
--a, --addrs            Dump all addresses (flat list).
--K, --keysforaddrs= f  Dump private keys for addresses listed in file 'f'.
--P, --passwd-file=  f  Get passphrase from file 'f'.
--q, --quiet            Produce quieter output; suppress some warnings.
--S, --stdout           Dump to stdout rather than file.
-"""
-}
-
-cmd_args = opt.opts.init(opts_data)
-opt.opts.die_on_incompatible_opts(['json','keys','addrs','keysforaddrs'])
-
-if len(cmd_args) == 1:
-	from mmgen.util import check_infile
-	check_infile(cmd_args[0])
-else:
-	opt.opts.usage(opts_data)
-
-if (not opt.json and not opt.keys and not opt.addrs and not opt.keysforaddrs):
-			opt.opts.usage(opts_data)
-
-# from the SlowAES project, http://code.google.com/p/slowaes (aes.py)
-
-def append_PKCS7_padding(s):
-	"""return s padded to a multiple of 16-bytes by PKCS7 padding"""
-	numpads = 16 - (len(s)%16)
-	return s + numpads*chr(numpads)
-
-def strip_PKCS7_padding(s):
-	"""return s stripped of PKCS7 padding"""
-	if len(s)%16 or not s:
-		raise ValueError("String of len %d can't be PCKS7-padded" % len(s))
-	numpads = ord(s[-1])
-	if numpads > 16:
-		raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1])
-	return s[:-numpads]
-
-class AES(object):
-	# valid key sizes
-	keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32)
-
-	# Rijndael S-box
-	sbox =  [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
-			0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
-			0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
-			0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
-			0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
-			0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
-			0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
-			0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
-			0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
-			0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
-			0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
-			0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
-			0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
-			0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
-			0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
-			0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
-			0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
-			0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
-			0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
-			0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
-			0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
-			0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
-			0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
-			0x54, 0xbb, 0x16]
-
-	# Rijndael Inverted S-box
-	rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3,
-			0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f,
-			0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54,
-			0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b,
-			0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24,
-			0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8,
-			0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,
-			0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
-			0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab,
-			0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3,
-			0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1,
-			0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41,
-			0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6,
-			0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9,
-			0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d,
-			0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b ,
-			0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,
-			0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07,
-			0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60,
-			0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
-			0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5,
-			0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b,
-			0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55,
-			0x21, 0x0c, 0x7d]
-
-	def getSBoxValue(self,num):
-		"""Retrieves a given S-Box Value"""
-		return self.sbox[num]
-
-	def getSBoxInvert(self,num):
-		"""Retrieves a given Inverted S-Box Value"""
-		return self.rsbox[num]
-
-	def rotate(self, word):
-		""" Rijndael's key schedule rotate operation.
-
-        Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d
-        Word is an char list of size 4 (32 bits overall).
-		"""
-		return word[1:] + word[:1]
-
-	# Rijndael Rcon
-	Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
-			0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97,
-			0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
-			0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66,
-			0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
-			0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
-			0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
-			0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61,
-			0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
-			0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
-			0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
-			0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
-			0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,
-			0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,
-			0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
-			0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
-			0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4,
-			0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
-			0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08,
-			0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
-			0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
-			0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2,
-			0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74,
-			0xe8, 0xcb ]
-
-	def getRconValue(self, num):
-		"""Retrieves a given Rcon Value"""
-		return self.Rcon[num]
-
-	def core(self, word, iteration):
-		"""Key schedule core."""
-		# rotate the 32-bit word 8 bits to the left
-		word = self.rotate(word)
-		# apply S-Box substitution on all 4 parts of the 32-bit word
-		for i in range(4):
-			word[i] = self.getSBoxValue(word[i])
-		# XOR the output of the rcon operation with i to the first part
-		# (leftmost) only
-		word[0] = word[0] ^ self.getRconValue(iteration)
-		return word
-
-	def expandKey(self, key, size, expandedKeySize):
-		"""Rijndael's key expansion.
-
-        Expands an 128,192,256 key into an 176,208,240 bytes key
-
-        expandedKey is a char list of large enough size,
-        key is the non-expanded key.
-		"""
-		# current expanded keySize, in bytes
-		currentSize = 0
-		rconIteration = 1
-		expandedKey = [0] * expandedKeySize
-
-		# set the 16, 24, 32 bytes of the expanded key to the input key
-		for j in range(size):
-			expandedKey[j] = key[j]
-		currentSize += size
-
-		while currentSize < expandedKeySize:
-			# assign the previous 4 bytes to the temporary value t
-			t = expandedKey[currentSize-4:currentSize]
-
-			# every 16,24,32 bytes we apply the core schedule to t
-			# and increment rconIteration afterwards
-			if currentSize % size == 0:
-				t = self.core(t, rconIteration)
-				rconIteration += 1
-			# For 256-bit keys, we add an extra sbox to the calculation
-			if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16):
-				for l in range(4): t[l] = self.getSBoxValue(t[l])
-
-			# We XOR t with the four-byte block 16,24,32 bytes before the new
-			# expanded key.  This becomes the next four bytes in the expanded
-			# key.
-			for m in range(4):
-				expandedKey[currentSize] = expandedKey[currentSize - size] ^ \
-						t[m]
-				currentSize += 1
-
-		return expandedKey
-
-	def addRoundKey(self, state, roundKey):
-		"""Adds (XORs) the round key to the state."""
-		for i in range(16):
-			state[i] ^= roundKey[i]
-		return state
-
-	def createRoundKey(self, expandedKey, roundKeyPointer):
-		"""Create a round key.
-        Creates a round key from the given expanded key and the
-        position within the expanded key.
-		"""
-		roundKey = [0] * 16
-		for i in range(4):
-			for j in range(4):
-				roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j]
-		return roundKey
-
-	def galois_multiplication(self, a, b):
-		"""Galois multiplication of 8 bit characters a and b."""
-		p = 0
-		for counter in range(8):
-			if b & 1: p ^= a
-			hi_bit_set = a & 0x80
-			a <<= 1
-			# keep a 8 bit
-			a &= 0xFF
-			if hi_bit_set:
-				a ^= 0x1b
-			b >>= 1
-		return p
-
-	#
-	# substitute all the values from the state with the value in the SBox
-	# using the state value as index for the SBox
-	#
-	def subBytes(self, state, isInv):
-		if isInv: getter = self.getSBoxInvert
-		else: getter = self.getSBoxValue
-		for i in range(16): state[i] = getter(state[i])
-		return state
-
-	# iterate over the 4 rows and call shiftRow() with that row
-	def shiftRows(self, state, isInv):
-		for i in range(4):
-			state = self.shiftRow(state, i*4, i, isInv)
-		return state
-
-	# each iteration shifts the row to the left by 1
-	def shiftRow(self, state, statePointer, nbr, isInv):
-		for i in range(nbr):
-			if isInv:
-				state[statePointer:statePointer+4] = \
-						state[statePointer+3:statePointer+4] + \
-						state[statePointer:statePointer+3]
-			else:
-				state[statePointer:statePointer+4] = \
-						state[statePointer+1:statePointer+4] + \
-						state[statePointer:statePointer+1]
-		return state
-
-	# galois multiplication of the 4x4 matrix
-	def mixColumns(self, state, isInv):
-		# iterate over the 4 columns
-		for i in range(4):
-			# construct one column by slicing over the 4 rows
-			column = state[i:i+16:4]
-			# apply the mixColumn on one column
-			column = self.mixColumn(column, isInv)
-			# put the values back into the state
-			state[i:i+16:4] = column
-
-		return state
-
-	# galois multiplication of 1 column of the 4x4 matrix
-	def mixColumn(self, column, isInv):
-		if isInv: mult = [14, 9, 13, 11]
-		else: mult = [2, 1, 1, 3]
-		cpy = list(column)
-		g = self.galois_multiplication
-
-		column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \
-					g(cpy[2], mult[2]) ^ g(cpy[1], mult[3])
-		column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \
-					g(cpy[3], mult[2]) ^ g(cpy[2], mult[3])
-		column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \
-					g(cpy[0], mult[2]) ^ g(cpy[3], mult[3])
-		column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \
-					g(cpy[1], mult[2]) ^ g(cpy[0], mult[3])
-		return column
-
-	# applies the 4 operations of the forward round in sequence
-	def aes_round(self, state, roundKey):
-		state = self.subBytes(state, False)
-		state = self.shiftRows(state, False)
-		state = self.mixColumns(state, False)
-		state = self.addRoundKey(state, roundKey)
-		return state
-
-	# applies the 4 operations of the inverse round in sequence
-	def aes_invRound(self, state, roundKey):
-		state = self.shiftRows(state, True)
-		state = self.subBytes(state, True)
-		state = self.addRoundKey(state, roundKey)
-		state = self.mixColumns(state, True)
-		return state
-
-	# Perform the initial operations, the standard round, and the final
-	# operations of the forward aes, creating a round key for each round
-	def aes_main(self, state, expandedKey, nbrRounds):
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
-		i = 1
-		while i < nbrRounds:
-			state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i))
-			i += 1
-		state = self.subBytes(state, False)
-		state = self.shiftRows(state, False)
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
-		return state
-
-	# Perform the initial operations, the standard round, and the final
-	# operations of the inverse aes, creating a round key for each round
-	def aes_invMain(self, state, expandedKey, nbrRounds):
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds))
-		i = nbrRounds - 1
-		while i > 0:
-			state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i))
-			i -= 1
-		state = self.shiftRows(state, True)
-		state = self.subBytes(state, True)
-		state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0))
-		return state
-
-	# encrypts a 128 bit input block against the given key of size specified
-	def encrypt(self, iput, key, size):
-		output = [0] * 16
-		# the number of rounds
-		nbrRounds = 0
-		# the 128 bit block to encode
-		block = [0] * 16
-		# set the number of rounds
-		if size == self.keySize["SIZE_128"]: nbrRounds = 10
-		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
-		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
-		else: return None
-
-		# the expanded keySize
-		expandedKeySize = 16*(nbrRounds+1)
-
-		# Set the block values, for the block:
-		# a0,0 a0,1 a0,2 a0,3
-		# a1,0 a1,1 a1,2 a1,3
-		# a2,0 a2,1 a2,2 a2,3
-		# a3,0 a3,1 a3,2 a3,3
-		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
-		#
-		# iterate over the columns
-		for i in range(4):
-			# iterate over the rows
-			for j in range(4):
-				block[(i+(j*4))] = iput[(i*4)+j]
-
-		# expand the key into an 176, 208, 240 bytes key
-		# the expanded key
-		expandedKey = self.expandKey(key, size, expandedKeySize)
-
-		# encrypt the block using the expandedKey
-		block = self.aes_main(block, expandedKey, nbrRounds)
-
-		# unmap the block again into the output
-		for k in range(4):
-			# iterate over the rows
-			for l in range(4):
-				output[(k*4)+l] = block[(k+(l*4))]
-		return output
-
-	# decrypts a 128 bit input block against the given key of size specified
-	def decrypt(self, iput, key, size):
-		output = [0] * 16
-		# the number of rounds
-		nbrRounds = 0
-		# the 128 bit block to decode
-		block = [0] * 16
-		# set the number of rounds
-		if size == self.keySize["SIZE_128"]: nbrRounds = 10
-		elif size == self.keySize["SIZE_192"]: nbrRounds = 12
-		elif size == self.keySize["SIZE_256"]: nbrRounds = 14
-		else: return None
-
-		# the expanded keySize
-		expandedKeySize = 16*(nbrRounds+1)
-
-		# Set the block values, for the block:
-		# a0,0 a0,1 a0,2 a0,3
-		# a1,0 a1,1 a1,2 a1,3
-		# a2,0 a2,1 a2,2 a2,3
-		# a3,0 a3,1 a3,2 a3,3
-		# the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
-
-		# iterate over the columns
-		for i in range(4):
-			# iterate over the rows
-			for j in range(4):
-				block[(i+(j*4))] = iput[(i*4)+j]
-		# expand the key into an 176, 208, 240 bytes key
-		expandedKey = self.expandKey(key, size, expandedKeySize)
-		# decrypt the block using the expandedKey
-		block = self.aes_invMain(block, expandedKey, nbrRounds)
-		# unmap the block again into the output
-		for k in range(4):
-			# iterate over the rows
-			for l in range(4):
-				output[(k*4)+l] = block[(k+(l*4))]
-		return output
-
-class AESModeOfOperation(object):
-
-	aes = AES()
-
-	# structure of supported modes of operation
-	modeOfOperation = dict(OFB=0, CFB=1, CBC=2)
-
-	# converts a 16 character string into a number array
-	def convertString(self, string, start, end, mode):
-		if end - start > 16: end = start + 16
-		if mode == self.modeOfOperation["CBC"]: ar = [0] * 16
-		else: ar = []
-
-		i = start
-		j = 0
-		while len(ar) < end - start:
-			ar.append(0)
-		while i < end:
-			ar[j] = ord(string[i])
-			j += 1
-			i += 1
-		return ar
-
-	# Mode of Operation Encryption
-	# stringIn - Input String
-	# mode - mode of type modeOfOperation
-	# hexKey - a hex key of the bit length size
-	# size - the bit length of the key
-	# hexIV - the 128 bit hex Initilization Vector
-	def encrypt(self, stringIn, mode, key, size, IV):
-		if len(key) % size:
-			return None
-		if len(IV) % 16:
-			return None
-		# the AES input/output
-		plaintext = []
-		iput = [0] * 16
-		output = []
-		ciphertext = [0] * 16
-		# the output cipher string
-		cipherOut = []
-		# char firstRound
-		firstRound = True
-		if stringIn != None:
-			for j in range(int(math.ceil(float(len(stringIn))/16))):
-				start = j*16
-				end = j*16+16
-				if  end > len(stringIn):
-					end = len(stringIn)
-				plaintext = self.convertString(stringIn, start, end, mode)
-				# print 'PT@%s:%s' % (j, plaintext)
-				if mode == self.modeOfOperation["CFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(plaintext)-1 < i:
-							ciphertext[i] = 0 ^ output[i]
-						elif len(output)-1 < i:
-							ciphertext[i] = plaintext[i] ^ 0
-						elif len(plaintext)-1 < i and len(output) < i:
-							ciphertext[i] = 0 ^ 0
-						else:
-							ciphertext[i] = plaintext[i] ^ output[i]
-					for k in range(end-start):
-						cipherOut.append(ciphertext[k])
-					iput = ciphertext
-				elif mode == self.modeOfOperation["OFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(plaintext)-1 < i:
-							ciphertext[i] = 0 ^ output[i]
-						elif len(output)-1 < i:
-							ciphertext[i] = plaintext[i] ^ 0
-						elif len(plaintext)-1 < i and len(output) < i:
-							ciphertext[i] = 0 ^ 0
-						else:
-							ciphertext[i] = plaintext[i] ^ output[i]
-					for k in range(end-start):
-						cipherOut.append(ciphertext[k])
-					iput = output
-				elif mode == self.modeOfOperation["CBC"]:
-					for i in range(16):
-						if firstRound:
-							iput[i] =  plaintext[i] ^ IV[i]
-						else:
-							iput[i] =  plaintext[i] ^ ciphertext[i]
-					# print 'IP@%s:%s' % (j, iput)
-					firstRound = False
-					ciphertext = self.aes.encrypt(iput, key, size)
-					# always 16 bytes because of the padding for CBC
-					for k in range(16):
-						cipherOut.append(ciphertext[k])
-		return mode, len(stringIn), cipherOut
-
-	# Mode of Operation Decryption
-	# cipherIn - Encrypted String
-	# originalsize - The unencrypted string length - required for CBC
-	# mode - mode of type modeOfOperation
-	# key - a number array of the bit length size
-	# size - the bit length of the key
-	# IV - the 128 bit number array Initilization Vector
-	def decrypt(self, cipherIn, originalsize, mode, key, size, IV):
-		# cipherIn = unescCtrlChars(cipherIn)
-		if len(key) % size:
-			return None
-		if len(IV) % 16:
-			return None
-		# the AES input/output
-		ciphertext = []
-		iput = []
-		output = []
-		plaintext = [0] * 16
-		# the output plain text string
-		stringOut = ''
-		# char firstRound
-		firstRound = True
-		if cipherIn != None:
-			for j in range(int(math.ceil(float(len(cipherIn))/16))):
-				start = j*16
-				end = j*16+16
-				if j*16+16 > len(cipherIn):
-					end = len(cipherIn)
-				ciphertext = cipherIn[start:end]
-				if mode == self.modeOfOperation["CFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(output)-1 < i:
-							plaintext[i] = 0 ^ ciphertext[i]
-						elif len(ciphertext)-1 < i:
-							plaintext[i] = output[i] ^ 0
-						elif len(output)-1 < i and len(ciphertext) < i:
-							plaintext[i] = 0 ^ 0
-						else:
-							plaintext[i] = output[i] ^ ciphertext[i]
-					for k in range(end-start):
-						stringOut += chr(plaintext[k])
-					iput = ciphertext
-				elif mode == self.modeOfOperation["OFB"]:
-					if firstRound:
-						output = self.aes.encrypt(IV, key, size)
-						firstRound = False
-					else:
-						output = self.aes.encrypt(iput, key, size)
-					for i in range(16):
-						if len(output)-1 < i:
-							plaintext[i] = 0 ^ ciphertext[i]
-						elif len(ciphertext)-1 < i:
-							plaintext[i] = output[i] ^ 0
-						elif len(output)-1 < i and len(ciphertext) < i:
-							plaintext[i] = 0 ^ 0
-						else:
-							plaintext[i] = output[i] ^ ciphertext[i]
-					for k in range(end-start):
-						stringOut += chr(plaintext[k])
-					iput = output
-				elif mode == self.modeOfOperation["CBC"]:
-					output = self.aes.decrypt(ciphertext, key, size)
-					for i in range(16):
-						if firstRound:
-							plaintext[i] = IV[i] ^ output[i]
-						else:
-							plaintext[i] = iput[i] ^ output[i]
-					firstRound = False
-					if originalsize is not None and originalsize < end:
-						for k in range(originalsize-start):
-							stringOut += chr(plaintext[k])
-					else:
-						for k in range(end-start):
-							stringOut += chr(plaintext[k])
-					iput = ciphertext
-		return stringOut
-
-# end of aes.py code
-
-# pywallet crypter implementation
-
-crypter = None
-
-try:
-	from Crypto.Cipher import AES
-	crypter = 'pycrypto'
-except:
-	pass
-
-class Crypter_pycrypto( object ):
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		data = vKeyData + vSalt
-		for i in xrange(nDerivIterations):
-			data = hashlib.sha512(data).digest()
-		self.SetKey(data[0:32])
-		self.SetIV(data[32:32+16])
-		return len(data)
-
-	def SetKey(self, key):
-		self.chKey = key
-
-	def SetIV(self, iv):
-		self.chIV = iv[0:16]
-
-	def Encrypt(self, data):
-		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32]
-
-	def Decrypt(self, data):
-		return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32]
-
-try:
-	if not crypter:
-		import ctypes
-		import ctypes.util
-		ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
-		crypter = 'ssl'
-except:
-	pass
-
-class Crypter_ssl(object):
-	def __init__(self):
-		self.chKey = ctypes.create_string_buffer (32)
-		self.chIV = ctypes.create_string_buffer (16)
-
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		strKeyData = ctypes.create_string_buffer (vKeyData)
-		chSalt = ctypes.create_string_buffer (vSalt)
-		return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData,
-			len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV))
-
-	def SetKey(self, key):
-		self.chKey = ctypes.create_string_buffer(key)
-
-	def SetIV(self, iv):
-		self.chIV = ctypes.create_string_buffer(iv)
-
-	def Encrypt(self, data):
-		buf = ctypes.create_string_buffer(len(data) + 16)
-		written = ctypes.c_int(0)
-		final = ctypes.c_int(0)
-		ctx = ssl.EVP_CIPHER_CTX_new()
-		ssl.EVP_CIPHER_CTX_init(ctx)
-		ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
-		ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
-		output = buf.raw[:written.value]
-		ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final))
-		output += buf.raw[:final.value]
-		return output
-
-	def Decrypt(self, data):
-		buf = ctypes.create_string_buffer(len(data) + 16)
-		written = ctypes.c_int(0)
-		final = ctypes.c_int(0)
-		ctx = ssl.EVP_CIPHER_CTX_new()
-		ssl.EVP_CIPHER_CTX_init(ctx)
-		ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV)
-		ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data))
-		output = buf.raw[:written.value]
-		ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final))
-		output += buf.raw[:final.value]
-		return output
-
-class Crypter_pure(object):
-	def __init__(self):
-		self.m = AESModeOfOperation()
-		self.cbc = self.m.modeOfOperation["CBC"]
-		self.sz = self.m.aes.keySize["SIZE_256"]
-
-	def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod):
-		if nDerivationMethod != 0:
-			return 0
-		data = vKeyData + vSalt
-		for i in xrange(nDerivIterations):
-			data = hashlib.sha512(data).digest()
-		self.SetKey(data[0:32])
-		self.SetIV(data[32:32+16])
-		return len(data)
-
-	def SetKey(self, key):
-		self.chKey = [ord(i) for i in key]
-
-	def SetIV(self, iv):
-		self.chIV = [ord(i) for i in iv]
-
-	def Encrypt(self, data):
-		mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV)
-		return ''.join(map(chr, cypher))
-
-	def Decrypt(self, data):
-		chData = [ord(i) for i in data]
-		return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV)
-
-# secp256k1
-
-_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
-_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
-_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
-_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
-_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
-_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
-
-# python-ecdsa code (EC_KEY implementation)
-
-class CurveFp( object ):
-	def __init__( self, p, a, b ):
-		self.__p = p
-		self.__a = a
-		self.__b = b
-
-	def p( self ):
-		return self.__p
-
-	def a( self ):
-		return self.__a
-
-	def b( self ):
-		return self.__b
-
-	def contains_point( self, x, y ):
-		return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0
-
-class Point( object ):
-	def __init__( self, curve, x, y, order = None ):
-		self.__curve = curve
-		self.__x = x
-		self.__y = y
-		self.__order = order
-		if self.__curve: assert self.__curve.contains_point( x, y )
-		if order: assert self * order == INFINITY
-
-	def __add__( self, other ):
-		if other == INFINITY: return self
-		if self == INFINITY: return other
-		assert self.__curve == other.__curve
-		if self.__x == other.__x:
-			if ( self.__y + other.__y ) % self.__curve.p() == 0:
-				return INFINITY
-			else:
-				return self.double()
-
-		p = self.__curve.p()
-		l = ( ( other.__y - self.__y ) * \
-					inverse_mod( other.__x - self.__x, p ) ) % p
-		x3 = ( l * l - self.__x - other.__x ) % p
-		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
-		return Point( self.__curve, x3, y3 )
-
-	def __mul__( self, other ):
-		def leftmost_bit( x ):
-			assert x > 0
-			result = 1L
-			while result <= x: result = 2 * result
-			return result / 2
-
-		e = other
-		if self.__order: e = e % self.__order
-		if e == 0: return INFINITY
-		if self == INFINITY: return INFINITY
-		assert e > 0
-		e3 = 3 * e
-		negative_self = Point( self.__curve, self.__x, -self.__y, self.__order )
-		i = leftmost_bit( e3 ) / 2
-		result = self
-		while i > 1:
-			result = result.double()
-			if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self
-			if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self
-			i = i / 2
-		return result
-
-	def __rmul__( self, other ):
-		return self * other
-
-	def __str__( self ):
-		if self == INFINITY: return "infinity"
-		return "(%d,%d)" % ( self.__x, self.__y )
-
-	def double( self ):
-		if self == INFINITY:
-			return INFINITY
-
-		p = self.__curve.p()
-		a = self.__curve.a()
-		l = ( ( 3 * self.__x * self.__x + a ) * \
-					inverse_mod( 2 * self.__y, p ) ) % p
-		x3 = ( l * l - 2 * self.__x ) % p
-		y3 = ( l * ( self.__x - x3 ) - self.__y ) % p
-		return Point( self.__curve, x3, y3 )
-
-	def x( self ):
-		return self.__x
-
-	def y( self ):
-		return self.__y
-
-	def curve( self ):
-		return self.__curve
-
-	def order( self ):
-		return self.__order
-
-INFINITY = Point( None, None, None )
-
-def inverse_mod( a, m ):
-	if a < 0 or m <= a: a = a % m
-	c, d = a, m
-	uc, vc, ud, vd = 1, 0, 0, 1
-	while c != 0:
-		q, c, d = divmod( d, c ) + ( c, )
-		uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc
-	assert d == 1
-	if ud > 0: return ud
-	else: return ud + m
-
-class Signature( object ):
-	def __init__( self, r, s ):
-		self.r = r
-		self.s = s
-
-class Public_key( object ):
-	def __init__( self, generator, point ):
-		self.curve = generator.curve()
-		self.generator = generator
-		self.point = point
-		n = generator.order()
-		if not n:
-			raise RuntimeError, "Generator point must have order."
-		if not n * point == INFINITY:
-			raise RuntimeError, "Generator point order is bad."
-		if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y():
-			raise RuntimeError, "Generator point has x or y out of range."
-
-	def verifies( self, hash, signature ):
-		G = self.generator
-		n = G.order()
-		r = signature.r
-		s = signature.s
-		if r < 1 or r > n-1: return False
-		if s < 1 or s > n-1: return False
-		c = inverse_mod( s, n )
-		u1 = ( hash * c ) % n
-		u2 = ( r * c ) % n
-		xy = u1 * G + u2 * self.point
-		v = xy.x() % n
-		return v == r
-
-class Private_key( object ):
-	def __init__( self, public_key, secret_multiplier ):
-		self.public_key = public_key
-		self.secret_multiplier = secret_multiplier
-
-	def der( self ):
-		hex_der_key = '06052b8104000a30740201010420' + \
-			'%064x' % self.secret_multiplier + \
-			'a00706052b8104000aa14403420004' + \
-			'%064x' % self.public_key.point.x() + \
-			'%064x' % self.public_key.point.y()
-		return hex_der_key.decode('hex')
-
-	def sign( self, hash, random_k ):
-		G = self.public_key.generator
-		n = G.order()
-		k = random_k % n
-		p1 = k * G
-		r = p1.x()
-		if r == 0: raise RuntimeError, "amazingly unlucky random number r"
-		s = ( inverse_mod( k, n ) * \
-					( hash + ( self.secret_multiplier * r ) % n ) ) % n
-		if s == 0: raise RuntimeError, "amazingly unlucky random number s"
-		return Signature( r, s )
-
-class EC_KEY(object):
-	def __init__( self, secret ):
-		curve = CurveFp( _p, _a, _b )
-		generator = Point( curve, _Gx, _Gy, _r )
-		self.pubkey = Public_key( generator, generator * secret )
-		self.privkey = Private_key( self.pubkey, secret )
-		self.secret = secret
-
-# end of python-ecdsa code
-
-# pywallet openssl private key implementation
-
-def i2d_ECPrivateKey(pkey, compressed=False):
-	if compressed:
-		key = '3081d30201010420' + \
-			'%064x' % pkey.secret + \
-			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
-			'%064x' % _p + \
-			'3006040100040107042102' + \
-			'%064x' % _Gx + \
-			'022100' + \
-			'%064x' % _r + \
-			'020101a124032200'
-	else:
-		key = '308201130201010420' + \
-			'%064x' % pkey.secret + \
-			'a081a53081a2020101302c06072a8648ce3d0101022100' + \
-			'%064x' % _p + \
-			'3006040100040107044104' + \
-			'%064x' % _Gx + \
-			'%064x' % _Gy + \
-			'022100' + \
-			'%064x' % _r + \
-			'020101a144034200'
-
-	return key.decode('hex') + i2o_ECPublicKey(pkey, compressed)
-
-def i2o_ECPublicKey(pkey, compressed=False):
-	# public keys are 65 bytes long (520 bits)
-	# 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate
-	# 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed
-	# compressed keys: <sign> <x> where <sign> is 0x02 if y is even and 0x03 if y is odd
-	if compressed:
-		if pkey.pubkey.point.y() & 1:
-			key = '03' + '%064x' % pkey.pubkey.point.x()
-		else:
-			key = '02' + '%064x' % pkey.pubkey.point.x()
-	else:
-		key = '04' + \
-			'%064x' % pkey.pubkey.point.x() + \
-			'%064x' % pkey.pubkey.point.y()
-
-	return key.decode('hex')
-
-# bitcointools hashes and base58 implementation
-
-def hash_160(public_key):
-	md = hashlib.new('ripemd160')
-	md.update(hashlib.sha256(public_key).digest())
-	return md.digest()
-
-def public_key_to_bc_address(public_key):
-	h160 = hash_160(public_key)
-	return hash_160_to_bc_address(h160)
-
-def hash_160_to_bc_address(h160):
-	vh160 = chr(addrtype) + h160
-	h = Hash(vh160)
-	addr = vh160 + h[0:4]
-	return b58encode(addr)
-
-def bc_address_to_hash_160(addr):
-	bytes = b58decode(addr, 25)
-	return bytes[1:21]
-
-__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
-__b58base = len(__b58chars)
-
-def b58encode(v):
-	""" encode v, which is a string of bytes, to base58.
-	"""
-
-	long_value = 0L
-	for (i, c) in enumerate(v[::-1]):
-		long_value += (256**i) * ord(c)
-
-	result = ''
-	while long_value >= __b58base:
-		div, mod = divmod(long_value, __b58base)
-		result = __b58chars[mod] + result
-		long_value = div
-	result = __b58chars[long_value] + result
-
-	# Bitcoin does a little leading-zero-compression:
-	# leading 0-bytes in the input become leading-1s
-	nPad = 0
-	for c in v:
-		if c == '\0': nPad += 1
-		else: break
-
-	return (__b58chars[0]*nPad) + result
-
-def b58decode(v, length):
-	""" decode v into a string of len bytes
-	"""
-	long_value = 0L
-	for (i, c) in enumerate(v[::-1]):
-		long_value += __b58chars.find(c) * (__b58base**i)
-
-	result = ''
-	while long_value >= 256:
-		div, mod = divmod(long_value, 256)
-		result = chr(mod) + result
-		long_value = div
-	result = chr(long_value) + result
-
-	nPad = 0
-	for c in v:
-		if c == __b58chars[0]: nPad += 1
-		else: break
-
-	result = chr(0)*nPad + result
-	if length is not None and len(result) != length:
-		return None
-
-	return result
-
-# end of bitcointools base58 implementation
-
-
-# address handling code
-
-def Hash(data):
-	return hashlib.sha256(hashlib.sha256(data).digest()).digest()
-
-def EncodeBase58Check(secret):
-	hash = Hash(secret)
-	return b58encode(secret + hash[0:4])
-
-def DecodeBase58Check(sec):
-	vchRet = b58decode(sec, None)
-	secret = vchRet[0:-4]
-	csum = vchRet[-4:]
-	hash = Hash(secret)
-	cs32 = hash[0:4]
-	if cs32 != csum:
-		return None
-	else:
-		return secret
-
-def PrivKeyToSecret(privkey):
-	if len(privkey) == 279:
-		return privkey[9:9+32]
-	else:
-		return privkey[8:8+32]
-
-def SecretToASecret(secret, compressed=False):
-	vchIn = chr((addrtype+128)&255) + secret
-	if compressed: vchIn += '\01'
-	return EncodeBase58Check(vchIn)
-
-def ASecretToSecret(sec):
-	vch = DecodeBase58Check(sec)
-	if vch and vch[0] == chr((addrtype+128)&255):
-		return vch[1:]
-	else:
-		return False
-
-def regenerate_key(sec):
-	b = ASecretToSecret(sec)
-	if not b:
-		return False
-	b = b[0:32]
-	secret = int('0x' + b.encode('hex'), 16)
-	return EC_KEY(secret)
-
-def GetPubKey(pkey, compressed=False):
-	return i2o_ECPublicKey(pkey, compressed)
-
-def GetPrivKey(pkey, compressed=False):
-	return i2d_ECPrivateKey(pkey, compressed)
-
-def GetSecret(pkey):
-	return ('%064x' % pkey.secret).decode('hex')
-
-def is_compressed(sec):
-	b = ASecretToSecret(sec)
-	return len(b) == 33
-
-# bitcointools wallet.dat handling code
-
-def create_env(db_dir):
-	db_env = DBEnv(0)
-	r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER))
-	return db_env
-
-def parse_CAddress(vds):
-	d = {'ip':'0.0.0.0','port':0,'nTime': 0}
-	try:
-		d['nVersion'] = vds.read_int32()
-		d['nTime'] = vds.read_uint32()
-		d['nServices'] = vds.read_uint64()
-		d['pchReserved'] = vds.read_bytes(12)
-		d['ip'] = socket.inet_ntoa(vds.read_bytes(4))
-		d['port'] = vds.read_uint16()
-	except:
-		pass
-	return d
-
-def deserialize_CAddress(d):
-	return d['ip']+":"+str(d['port'])
-
-def parse_BlockLocator(vds):
-	d = { 'hashes' : [] }
-	nHashes = vds.read_compact_size()
-	for i in xrange(nHashes):
-		d['hashes'].append(vds.read_bytes(32))
-		return d
-
-def deserialize_BlockLocator(d):
-	result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec')
-	return result
-
-def parse_setting(setting, vds):
-	if setting[0] == "f":    # flag (boolean) settings
-		return str(vds.read_boolean())
-	elif setting[0:4] == "addr": # CAddress
-		d = parse_CAddress(vds)
-		return deserialize_CAddress(d)
-	elif setting == "nTransactionFee":
-		return vds.read_int64()
-	elif setting == "nLimitProcessors":
-		return vds.read_int32()
-	return 'unknown setting'
-
-class SerializationError(Exception):
-	""" Thrown when there's a problem deserializing or serializing """
-
-class BCDataStream(object):
-	def __init__(self):
-		self.input = None
-		self.read_cursor = 0
-
-	def clear(self):
-		self.input = None
-		self.read_cursor = 0
-
-	def write(self, bytes):    # Initialize with string of bytes
-		if self.input is None:
-			self.input = bytes
-		else:
-			self.input += bytes
-
-	def map_file(self, file, start):    # Initialize with bytes from file
-		self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
-		self.read_cursor = start
-	def seek_file(self, position):
-		self.read_cursor = position
-	def close_file(self):
-		self.input.close()
-
-	def read_string(self):
-		# Strings are encoded depending on length:
-		# 0 to 252 :    1-byte-length followed by bytes (if any)
-		# 253 to 65,535 : byte'253' 2-byte-length followed by bytes
-		# 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes
-		# ... and the Bitcoin client is coded to understand:
-		# greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string
-		# ... but I don't think it actually handles any strings that big.
-		if self.input is None:
-			raise SerializationError("call write(bytes) before trying to deserialize")
-
-		try:
-			length = self.read_compact_size()
-		except IndexError:
-			raise SerializationError("attempt to read past end of buffer")
-
-		return self.read_bytes(length)
-
-	def write_string(self, string):
-		# Length-encoded as with read-string
-		self.write_compact_size(len(string))
-		self.write(string)
-
-	def read_bytes(self, length):
-		try:
-			result = self.input[self.read_cursor:self.read_cursor+length]
-			self.read_cursor += length
-			return result
-		except IndexError:
-			raise SerializationError("attempt to read past end of buffer")
-
-		return ''
-
-	def read_boolean(self): return self.read_bytes(1)[0] != chr(0)
-	def read_int16(self): return self._read_num('<h')
-	def read_uint16(self): return self._read_num('<H')
-	def read_int32(self): return self._read_num('<i')
-	def read_uint32(self): return self._read_num('<I')
-	def read_int64(self): return self._read_num('<q')
-	def read_uint64(self): return self._read_num('<Q')
-
-	def write_boolean(self, val): return self.write(chr(1) if val else chr(0))
-	def write_int16(self, val): return self._write_num('<h', val)
-	def write_uint16(self, val): return self._write_num('<H', val)
-	def write_int32(self, val): return self._write_num('<i', val)
-	def write_uint32(self, val): return self._write_num('<I', val)
-	def write_int64(self, val): return self._write_num('<q', val)
-	def write_uint64(self, val): return self._write_num('<Q', val)
-
-	def read_compact_size(self):
-		size = ord(self.input[self.read_cursor])
-		self.read_cursor += 1
-		if size == 253:
-			size = self._read_num('<H')
-		elif size == 254:
-			size = self._read_num('<I')
-		elif size == 255:
-			size = self._read_num('<Q')
-		return size
-
-	def write_compact_size(self, size):
-		if size < 0:
-			raise SerializationError("attempt to write size < 0")
-		elif size < 253:
-			self.write(chr(size))
-		elif size < 2**16:
-			self.write('\xfd')
-			self._write_num('<H', size)
-		elif size < 2**32:
-			self.write('\xfe')
-			self._write_num('<I', size)
-		elif size < 2**64:
-			self.write('\xff')
-			self._write_num('<Q', size)
-
-	def _read_num(self, format):
-		(i,) = struct.unpack_from(format, self.input, self.read_cursor)
-		self.read_cursor += struct.calcsize(format)
-		return i
-
-	def _write_num(self, format, num):
-		s = struct.pack(format, num)
-		self.write(s)
-
-def open_wallet(db_env, db_file="wallet.dat", writable=False):
-	db = DB(db_env)
-	flags = DB_THREAD | (DB_CREATE if writable else DB_RDONLY)
-	try:
-		r = db.open(db_file, "main", DB_BTREE, flags)
-	except DBError:
-		r = True
-
-	if r is not None:
-		logging.error("Couldn't open " + db_file + "/main. Try quitting Bitcoin and running this again.")
-		sys.exit(1)
-
-	return db
-
-def parse_wallet(db, item_callback):
-	kds = BCDataStream()
-	vds = BCDataStream()
-
-	for (key, value) in db.items():
-		d = { }
-
-		kds.clear(); kds.write(key)
-		vds.clear(); vds.write(value)
-
-		type = kds.read_string()
-
-		d["__key__"] = key
-		d["__value__"] = value
-		d["__type__"] = type
-
-		try:
-			if type == "tx":
-				d["tx_id"] = kds.read_bytes(32)
-			elif type == "name":
-				d['hash'] = kds.read_string()
-				d['name'] = vds.read_string()
-			elif type == "version":
-				d['version'] = vds.read_uint32()
-			elif type == "minversion":
-				d['minversion'] = vds.read_uint32()
-			elif type == "setting":
-				d['setting'] = kds.read_string()
-				d['value'] = parse_setting(d['setting'], vds)
-			elif type == "key":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['private_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "wkey":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['private_key'] = vds.read_bytes(vds.read_compact_size())
-				d['created'] = vds.read_int64()
-				d['expires'] = vds.read_int64()
-				d['comment'] = vds.read_string()
-			elif type == "ckey":
-				d['public_key'] = kds.read_bytes(kds.read_compact_size())
-				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "mkey":
-				d['nID'] = kds.read_int32()
-				d['crypted_key'] = vds.read_bytes(vds.read_compact_size())
-				d['salt'] = vds.read_bytes(vds.read_compact_size())
-				d['nDerivationMethod'] = vds.read_int32()
-				d['nDeriveIterations'] = vds.read_int32()
-				d['vchOtherDerivationParameters'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "defaultkey":
-				d['key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "pool":
-				d['n'] = kds.read_int64()
-				d['nVersion'] = vds.read_int32()
-				d['nTime'] = vds.read_int64()
-				d['public_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "acc":
-				d['account'] = kds.read_string()
-				d['nVersion'] = vds.read_int32()
-				d['public_key'] = vds.read_bytes(vds.read_compact_size())
-			elif type == "acentry":
-				d['account'] = kds.read_string()
-				d['n'] = kds.read_uint64()
-				d['nVersion'] = vds.read_int32()
-				d['nCreditDebit'] = vds.read_int64()
-				d['nTime'] = vds.read_int64()
-				d['otherAccount'] = vds.read_string()
-				d['comment'] = vds.read_string()
-			elif type == "bestblock":
-				d['nVersion'] = vds.read_int32()
-				d.update(parse_BlockLocator(vds))
-
-			item_callback(type, d)
-
-		except Exception, e:
-			traceback.print_exc()
-			print("ERROR parsing wallet.dat, type %s" % type)
-			print("key data in hex: %s"%key.encode('hex_codec'))
-			print("value data in hex: %s"%value.encode('hex_codec'))
-			sys.exit(1)
-
-def update_wallet(db, type, data):
-	"""Write a single item to the wallet.
-    db must be open with writable=True.
-    type and data are the type code and data dictionary as parse_wallet would
-    give to item_callback.
-    data's __key__, __value__ and __type__ are ignored; only the primary data
-    fields are used.
-	"""
-	d = data
-	kds = BCDataStream()
-	vds = BCDataStream()
-
-	# Write the type code to the key
-	kds.write_string(type)
-	vds.write("")                         # Ensure there is something
-
-	try:
-		if type == "tx":
-			raise NotImplementedError("Writing items of type 'tx'")
-			kds.write(d['tx_id'])
-		elif type == "name":
-			kds.write_string(d['hash'])
-			vds.write_string(d['name'])
-		elif type == "version":
-			vds.write_uint32(d['version'])
-		elif type == "minversion":
-			vds.write_uint32(d['minversion'])
-		elif type == "setting":
-			raise NotImplementedError("Writing items of type 'setting'")
-			kds.write_string(d['setting'])
-			#d['value'] = parse_setting(d['setting'], vds)
-		elif type == "key":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['private_key'])
-		elif type == "wkey":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['private_key'])
-			vds.write_int64(d['created'])
-			vds.write_int64(d['expires'])
-			vds.write_string(d['comment'])
-		elif type == "ckey":
-			kds.write_string(d['public_key'])
-			vds.write_string(d['crypted_key'])
-		elif type == "defaultkey":
-			vds.write_string(d['key'])
-		elif type == "pool":
-			kds.write_int64(d['n'])
-			vds.write_int32(d['nVersion'])
-			vds.write_int64(d['nTime'])
-			vds.write_string(d['public_key'])
-		elif type == "acc":
-			kds.write_string(d['account'])
-			vds.write_int32(d['nVersion'])
-			vds.write_string(d['public_key'])
-		elif type == "acentry":
-			kds.write_string(d['account'])
-			kds.write_uint64(d['n'])
-			vds.write_int32(d['nVersion'])
-			vds.write_int64(d['nCreditDebit'])
-			vds.write_int64(d['nTime'])
-			vds.write_string(d['otherAccount'])
-			vds.write_string(d['comment'])
-		elif type == "bestblock":
-			vds.write_int32(d['nVersion'])
-			vds.write_compact_size(len(d['hashes']))
-			for h in d['hashes']:
-				vds.write(h)
-		else:
-			print "Unknown key type: "+type
-
-		# Write the key/value pair to the database
-		db.put(kds.input, vds.input)
-
-	except Exception, e:
-		print("ERROR writing to wallet.dat, type %s"%type)
-		print("data dictionary: %r"%data)
-		traceback.print_exc()
-
-
-def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transactions, transaction_filter):
-
-	db = open_wallet(db_env, db_file)
-
-	json_db['keys'] = []
-	json_db['pool'] = []
-	json_db['names'] = {}
-
-	def item_callback(type, d):
-
-		global password
-
-		if type == "name":
-			json_db['names'][d['hash']] = d['name']
-
-		elif type == "version":
-			json_db['version'] = d['version']
-
-		elif type == "minversion":
-			json_db['minversion'] = d['minversion']
-
-		elif type == "setting":
-			if not json_db.has_key('settings'): json_db['settings'] = {}
-			json_db["settings"][d['setting']] = d['value']
-
-		elif type == "defaultkey":
-			json_db['defaultkey'] = public_key_to_bc_address(d['key'])
-
-		elif type == "key":
-			addr = public_key_to_bc_address(d['public_key'])
-			compressed = d['public_key'][0] != '\04'
-			sec = SecretToASecret(PrivKeyToSecret(d['private_key']), compressed)
-			private_keys.append(sec)
-			json_db['keys'].append({'addr' : addr, 'sec' : sec})
-#            json_db['keys'].append({'addr' : addr, 'sec' : sec,
-#                'secret':PrivKeyToSecret(d['private_key']).encode('hex'),
-#                'pubkey':d['public_key'].encode('hex'),
-#                'privkey':d['private_key'].encode('hex')})
-
-		elif type == "wkey":
-			if not json_db.has_key('wkey'): json_db['wkey'] = []
-			json_db['wkey']['created'] = d['created']
-
-		elif type == "ckey":
-			addr = public_key_to_bc_address(d['public_key'])
-			ckey = d['crypted_key']
-			pubkey = d['public_key']
-			json_db['keys'].append( {'addr' : addr, 'ckey': ckey.encode('hex'), 'pubkey': pubkey.encode('hex') })
-
-		elif type == "mkey":
-			mkey = {}
-			mkey['nID'] = d['nID']
-			mkey['crypted_key'] = d['crypted_key'].encode('hex')
-			mkey['salt'] = d['salt'].encode('hex')
-			mkey['nDeriveIterations'] = d['nDeriveIterations']
-			mkey['nDerivationMethod'] = d['nDerivationMethod']
-			mkey['vchOtherDerivationParameters'] = d['vchOtherDerivationParameters'].encode('hex')
-			json_db['mkey'] = mkey
-
-			if password == None and (opt.json or opt.keysforaddrs or opt.keys):
-				if opt.passwd_file:
-					from mmgen.util import get_data_from_file
-					password = get_data_from_file(opt.passwd_file).rstrip()
-				else:
-					from mmgen.util import get_bitcoind_passphrase
-					password = get_bitcoind_passphrase("Enter password: ")
-
-			if password != None:
-				global crypter
-				if crypter == 'pycrypto':
-					crypter = Crypter_pycrypto()
-				elif crypter == 'ssl':
-					crypter = Crypter_ssl()
-				else:
-					crypter = Crypter_pure()
-					logging.warning("pycrypto or libssl not found, decryption may be slow")
-				res = crypter.SetKeyFromPassphrase(password, d['salt'], d['nDeriveIterations'], d['nDerivationMethod'])
-				if res == 0:
-					logging.error("Unsupported derivation method")
-					sys.exit(1)
-				masterkey = crypter.Decrypt(d['crypted_key'])
-				crypter.SetKey(masterkey)
-
-		elif type == "pool":
-			json_db['pool'].append( {'n': d['n'], 'addr': public_key_to_bc_address(d['public_key']), 'nTime' : d['nTime'] } )
-
-		elif type == "acc":
-			json_db['acc'] = d['account']
-#			msg("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key'])))
-
-		elif type == "acentry":
-			json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment'])
-
-		elif type == "bestblock":
-			json_db['bestblock'] = d['hashes'][0][::-1].encode('hex_codec')
-
-		else:
-			json_db[type] = 'unsupported'
-
-	parse_wallet(db, item_callback)
-
-	db.close()
-
-	for k in json_db['keys']:
-		addr = k['addr']
-		if addr in json_db['names'].keys():
-			k["label"] = json_db['names'][addr]
-		else:
-			k["reserve"] = 1
-
-	if 'mkey' in json_db.keys() and password != None:
-		check = True
-		for k in json_db['keys']:
-			ckey = k['ckey'].decode('hex')
-			public_key = k['pubkey'].decode('hex')
-			crypter.SetIV(Hash(public_key))
-			secret = crypter.Decrypt(ckey)
-			compressed = public_key[0] != '\04'
-
-			if check:
-				check = False
-				pkey = EC_KEY(int('0x' + secret.encode('hex'), 16))
-				if public_key != GetPubKey(pkey, compressed):
-					logging.error("wrong password")
-					sys.exit(1)
-
-			sec = SecretToASecret(secret, compressed)
-			k['sec'] = sec
-			k['secret'] = secret.encode('hex')
-			del(k['ckey'])
-			del(k['secret'])
-			del(k['pubkey'])
-			private_keys.append(sec)
-
-	del(json_db['pool'])
-	del(json_db['names'])
-
-
-# Non-portable.  For Windows, works only if supplied filename is in current dir
-
-# main()
-
-import os.path
-infile = os.path.abspath(cmd_args[0])
-db_dir,db_file = os.path.dirname(infile),os.path.basename(infile)
-
-#	print "[%s] [%s]" % (db_dir,db_file)
-
-db_env = create_env(db_dir)
-
-read_wallet(json_db, db_env, db_file, True, True, "")
-
-if json_db.get('minversion') > max_version:
-	print "Version mismatch (must be <= %d)" % max_version
-	exit(1)
-
-wallet_addrs = [i['addr'] for i in json_db['keys']]
-
-if opt.json:
-	data = [json.dumps(json_db, sort_keys=True, indent=4)]
-	ext,what = "json","json dump"
-
-elif opt.keys:
-	data = sorted([i['sec'] for i in json_db['keys']])
-	ext,what = "keys","private keys"
-
-elif opt.addrs:
-	data = sorted([i['addr'] for i in json_db['keys']])
-	ext,what = "addrs","addresses"
-
-elif opt.keysforaddrs:
-	from mmgen.util import get_lines_from_file
-	usr_addrs = set(get_lines_from_file(opt.keysforaddrs,"addresses",trim_comments=True))
-	data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
-	ext,what = "keys","private keys"
-	if len(data) < len(usr_addrs):
-		msg("Warning: not all requested keys found")
-
-len_arg = "%s" % len(wallet_addrs) \
-	if len(data) == len(wallet_addrs) or ext == "json" \
-		else "%s:%s" % (len(data),len(wallet_addrs))
-
-from mmgen.util import make_chksum_8,write_data_to_file
-wallet_id = make_chksum_8(str(sorted(wallet_addrs)))
-
-data = "\n".join(data) + "\n"
-
-# Output data
-of = "wd_%s[%s].%s" % (wallet_id,len_arg,ext)
-write_data_to_file(of, data, what, ask_overwrite=not opt.quiet)
-# 		outfile,
-# 		data,
-# 		desc="data",
-# 		ask_write=False,
-# 		ask_write_prompt="",
-# 		ask_write_default_yes=False,
-# 		ask_overwrite=True,
-# 		ask_tty=True,
-# 		no_tty=False,
-# 		silent=False

+ 11 - 15
mmgen/main_tool.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,14 +21,12 @@ mmgen-tool:  Perform various MMGen- and Bitcoin-related operations.
              Part of the MMGen suite
 """
 
-import sys
-import mmgen.globalvars as g
-import mmgen.opt as opt
+from mmgen.common import *
 import mmgen.tool as tool
 
 opts_data = {
-	'desc':    "Perform various {pnm}- and Bitcoin-related operations".format(pnm=g.proj_name),
-	'usage':   "[opts] <command> <command args>",
+	'desc':    'Perform various {pnm}- and Bitcoin-related operations'.format(pnm=g.proj_name),
+	'usage':   '[opts] <command> <command args>',
 	'options': """
 -d, --outdir=       d Specify an alternate directory 'd' for output
 -h, --help            Print this help message
@@ -46,27 +44,25 @@ command
 """.format(tool.cmd_help,g.prog_name)
 }
 
-cmd_args = opt.opts.init(opts_data,
+cmd_args = opts.init(opts_data,
 	add_opts=[
-		"no_keyconv",
-		"hidden_incog_input_params",
-		"in_fmt"
+		'no_keyconv',
+		'hidden_incog_input_params',
+		'in_fmt'
 		])
 
 if len(cmd_args) < 1:
-	opt.opts.usage()
+	opts.usage()
 	sys.exit(1)
 
 command = cmd_args.pop(0)
 
 if command not in tool.cmd_data:
-	from mmgen.util import msg
-	msg("'%s': No such command" % command)
-	sys.exit(1)
+	die(1,"'%s': no such command" % command)
 
 if cmd_args and cmd_args[0] == '--help':
 	tool.tool_usage(g.prog_name, command)
-	sys.exit(0)
+	sys.exit()
 
 args,kwargs = tool.process_args(g.prog_name, command, cmd_args)
 

+ 137 - 152
mmgen/main_txcreate.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,19 +21,17 @@ mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen
                 inputs and outputs
 """
 
-import sys
 from decimal import Decimal
 
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import dmsg
+from mmgen.common import *
 from mmgen.tx import *
+from mmgen.term import get_char
 
 pnm = g.proj_name
 
 opts_data = {
-	'desc':    "Create a BTC transaction with outputs to specified addresses",
-	'usage':   "[opts]  <addr,amt> ... [change addr] [addr file] ...",
+	'desc':    'Create a BTC transaction with outputs to specified addresses',
+	'usage':   '[opts]  <addr,amt> ... [change addr] [addr file] ...',
 	'options': """
 -h, --help            Print this help message
 -c, --comment-file= f Source the transaction's comment from file 'f'
@@ -63,8 +61,8 @@ with no amount on the command line.
 
 wmsg = {
 	'too_many_acct_addresses': """
-ERROR: More than one address found for account: "%s".
-Your "wallet.dat" file appears to have been altered by a non-{pnm} program.
+ERROR: More than one address found for account: '%s'.
+Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
 Please restore your tracking wallet from a backup or create a new one and
 re-import your addresses.
 """.strip().format(pnm=pnm),
@@ -106,44 +104,44 @@ was specified.
 
 def format_unspent_outputs_for_printing(out,sort_info,total):
 
-	pfs  = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
-	pout = [pfs % ("Num","TX id,Vout","Address","{pnm} ID".format(pnm=pnm),
-		"Amount (BTC)","Conf.","Age (days)", "Comment")]
+	pfs  = ' %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s'
+	pout = [pfs % ('Num','TX id,Vout','Address','{pnm} ID'.format(pnm=pnm),
+		'Amount (BTC)','Conf.','Age (days)', 'Comment')]
 
 	for n,i in enumerate(out):
-		addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
-		tx = " " * 63 + "=" \
-			if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
+		addr = '=' if i['skip'] == 'addr' and 'grouped' in sort_info else i['address']
+		tx = ' ' * 63 + '=' \
+			if i['skip'] == 'txid' and 'grouped' in sort_info else str(i['txid'])
 
-		s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,
-				i.mmid,i.amt,i.confirmations,i.days,i.comment)
+		s = pfs % (str(n+1)+')', tx+','+str(i['vout']),addr,
+				i['mmid'],i['amt'],i['confirmations'],i['days'],i['comment'])
 		pout.append(s.rstrip())
 
 	return \
-"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
-		make_timestr(), " ".join(sort_info), "\n".join(pout), total
+'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'.format(
+		make_timestr(), ' '.join(sort_info), '\n'.join(pout), total
 	)
 
 
 def sort_and_view(unspent):
 
-	def s_amt(i):   return i.amount
-	def s_txid(i):  return "%s %03s" % (i.txid,i.vout)
-	def s_addr(i):  return i.address
-	def s_age(i):   return i.confirmations
+	def s_amt(i):   return i['amount']
+	def s_txid(i):  return '%s %03s' % (i['txid'],i['vout'])
+	def s_addr(i):  return i['address']
+	def s_age(i):   return i['confirmations']
 	def s_mmgen(i):
-		if i.mmid:
-			return "{}:{:>0{w}}".format(
-				*i.mmid.split(":"), w=g.mmgen_idx_max_digits)
-		else: return "G" + i.comment
+		if i['mmid']:
+			return '{}:{:>0{w}}'.format(
+				*i['mmid'].split(':'), w=g.mmgen_idx_max_digits)
+		else: return 'G' + i['comment']
 
-	sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
+	sort,group,show_days,show_mmaddr,reverse = 'age',False,False,True,True
 	unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
 
-	total = trim_exponent(sum([i.amount for i in unspent]))
-	max_acct_len = max([len(i.mmid+" "+i.comment) for i in unspent])
+	total = trim_exponent(sum([i['amount'] for i in unspent]))
+	max_acct_len = max([len(i['mmid']+' '+i['comment']) for i in unspent])
 
-	hdr_fmt   = "UNSPENT OUTPUTS (sort order: %s)  Total BTC: %s"
+	hdr_fmt   = 'UNSPENT OUTPUTS (sort order: %s)  Total BTC: %s'
 	options_msg = """
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
@@ -151,85 +149,84 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 	prompt = \
 "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
 
-	mmid_w = max(len(i.mmid) for i in unspent)
+	mmid_w = max(len(i['mmid']) for i in unspent)
 	from copy import deepcopy
 	from mmgen.term import get_terminal_size
 
-	written_to_file_msg = ""
-	msg("")
+	written_to_file_msg = ''
+	msg('')
 
 	while True:
 		cols = get_terminal_size()[0]
 		if cols < g.min_screen_width:
-			msg(
-	"{pnl}-txcreate requires a screen at least {w} characters wide".format(
+			die(2,
+	'{pnl}-txcreate requires a screen at least {w} characters wide'.format(
 					pnl=pnm.lower(),w=g.min_screen_width))
-			sys.exit(2)
 
 		addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
 		acct_w   = min(max_acct_len, max(24,int(addr_w-10)))
 		btaddr_w = addr_w - acct_w - 1
 		tx_w = max(11,min(64, cols-addr_w-32))
-		txdots = "..." if tx_w < 64 else ""
-		fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
-		table_hdr = fs % ("Num","TX id  Vout","","Address","Amount (BTC)",
-							"Age(d)" if show_days else "Conf.")
+		txdots = ('','...')[tx_w < 64]
+		fs = ' %-4s %-' + str(tx_w) + 's %-2s %-' + str(addr_w) + 's %-13s %-s'
+		table_hdr = fs % ('Num','TX id  Vout','','Address','Amount (BTC)',
+							('Conf.','Age(d)')[show_days])
 
 		unsp = deepcopy(unspent)
-		for i in unsp: i.skip = ""
-		if group and (sort == "address" or sort == "txid"):
+		for i in unsp: i['skip'] = ''
+		if group and (sort == 'address' or sort == 'txid'):
 			for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
-				if sort == "address" and a.address == b.address: b.skip = "addr"
-				elif sort == "txid" and a.txid == b.txid:        b.skip = "txid"
+				if sort == 'address' and a['address'] == b['address']: b['skip'] = 'addr'
+				elif sort == 'txid' and a['txid'] == b['txid']:        b['skip'] = 'txid'
 
 		for i in unsp:
-			amt = str(trim_exponent(i.amount))
-			lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
-			i.amt = " "*lfill + amt
-			i.days = int(i.confirmations * g.mins_per_block / (60*24))
-			i.age = i.days if show_days else i.confirmations
-
-			if i.skip == "addr":
-				i.addr = "|" + "." * 33
+			amt = str(trim_exponent(i['amount']))
+			lfill = 3 - len(amt.split('.')[0]) if '.' in amt else 3 - len(amt)
+			i['amt'] = ' '*lfill + amt
+			i['days'] = int(i['confirmations'] * g.mins_per_block / (60*24))
+			i['age'] = i['days'] if show_days else i['confirmations']
+
+			addr_disp = (i['address'],'|' + '.'*33)[i['skip']=='addr']
+			mmid_disp = (i['mmid'],'.'*len(i['mmid']))[i['skip']=='addr']
+
+			if show_mmaddr:
+				dots = ('','..')[btaddr_w < len(i['address'])]
+				i['addr'] = '%s%s %s' % (
+					addr_disp[:btaddr_w-len(dots)],
+					dots, (
+					('{:<{w}} '.format(mmid_disp,w=mmid_w) if i['mmid'] else '')
+						+ i['comment'])[:acct_w]
+					)
 			else:
-				if show_mmaddr:
-					dots = ".." if btaddr_w < len(i.address) else ""
-					i.addr = "%s%s %s" % (
-						i.address[:btaddr_w-len(dots)],
-						dots, (
-						("{:<{w}} ".format(i.mmid,w=mmid_w) if i.mmid else "")
-							+ i.comment)[:acct_w]
-						)
-				else:
-					i.addr = i.address
-
-			i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
-					else i.txid[:tx_w-len(txdots)]+txdots
-
-		sort_info = ["reverse"] if reverse else []
-		sort_info.append(sort if sort else "unsorted")
-		if group and (sort == "address" or sort == "txid"):
-			sort_info.append("grouped")
-
-		out  = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
-		out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age)
+				i['addr'] = addr_disp
+
+			i['tx'] = ' ' * (tx_w-4) + '|...' if i['skip'] == 'txid' \
+					else i['txid'][:tx_w-len(txdots)]+txdots
+
+		sort_info = ([],['reverse'])[reverse]
+		sort_info.append(sort if sort else 'unsorted')
+		if group and (sort == 'address' or sort == 'txid'):
+			sort_info.append('grouped')
+
+		out  = [hdr_fmt % (' '.join(sort_info), total), table_hdr]
+		out += [fs % (str(n+1)+')',i['tx'],i['vout'],i['addr'],i['amt'],i['age'])
 					for n,i in enumerate(unsp)]
 
-		msg("\n".join(out) +"\n\n" + written_to_file_msg + options_msg)
-		written_to_file_msg = ""
+		msg('\n'.join(out) +'\n\n' + written_to_file_msg + options_msg)
+		written_to_file_msg = ''
 
 		skip_prompt = False
 
 		while True:
-			reply = get_char(prompt, immed_chars="atDdAMrgmeqpvw")
+			reply = get_char(prompt, immed_chars='atDdAMrgmeqpvw')
 
-			if   reply == 'a': unspent.sort(key=s_amt);  sort = "amount"
-			elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
+			if   reply == 'a': unspent.sort(key=s_amt);  sort = 'amount'
+			elif reply == 't': unspent.sort(key=s_txid); sort = 'txid'
 			elif reply == 'D': show_days = not show_days
-			elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
-			elif reply == 'A': unspent.sort(key=s_age);  sort = "age"
+			elif reply == 'd': unspent.sort(key=s_addr); sort = 'address'
+			elif reply == 'A': unspent.sort(key=s_age);  sort = 'age'
 			elif reply == 'M':
-				unspent.sort(key=s_mmgen); sort = "mmgen"
+				unspent.sort(key=s_mmgen); sort = 'mmgen'
 				show_mmaddr = True
 			elif reply == 'r':
 				unspent.reverse()
@@ -240,23 +237,23 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
 			elif reply == 'q': pass
 			elif reply == 'p':
 				d = format_unspent_outputs_for_printing(unsp,sort_info,total)
-				of = "listunspent[%s].out" % ",".join(sort_info)
-				write_data_to_file(of,d,"unspent outputs listing")
+				of = 'listunspent[%s].out' % ','.join(sort_info)
+				write_data_to_file(of,d,'unspent outputs listing')
 				written_to_file_msg = "Data written to '%s'\n\n" % of
 			elif reply == 'v':
-				do_pager("\n".join(out))
+				do_pager('\n'.join(out))
 				continue
 			elif reply == 'w':
 				data = format_unspent_outputs_for_printing(unsp,sort_info,total)
 				do_pager(data)
 				continue
 			else:
-				msg("\nInvalid input")
+				msg('\nInvalid input')
 				continue
 
 			break
 
-		msg("\n")
+		msg('\n')
 		if reply == 'q': break
 
 	return tuple(unspent)
@@ -274,24 +271,23 @@ def select_outputs(unspent,prompt):
 		if not selected: continue
 
 		if selected[-1] > len(unspent):
-			msg("Inputs must be less than %s" % len(unspent))
+			msg('Inputs must be less than %s' % len(unspent))
 			continue
 
 		return selected
 
 
 def mmaddr2btcaddr_unspent(unspent,mmaddr):
-	vmsg_r("Searching for {pnm} address {m} in wallet...".format(pnm=pnm,m=mmaddr))
-	m = [u for u in unspent if u.mmid == mmaddr]
+	vmsg_r('Searching for {pnm} address {m} in wallet...'.format(pnm=pnm,m=mmaddr))
+	m = [u for u in unspent if u['mmid'] == mmaddr]
 	if len(m) == 0:
-		vmsg("not found")
-		return "",""
+		vmsg('not found')
+		return '',''
 	elif len(m) > 1:
-		msg(wmsg['too_many_acct_addresses'] % acct); sys.exit(2)
+		die(2,wmsg['too_many_acct_addresses'] % acct)
 	else:
-		vmsg("success (%s)" % m[0].address)
+		vmsg('success (%s)' % m[0].address)
 		return m[0].address, m[0].comment
-	sys.exit()
 
 
 def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
@@ -304,14 +300,12 @@ def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
 			btcaddr = ail_f.mmaddr2btcaddr(mmaddr)
 			if btcaddr:
 				msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
-				if not keypress_confirm("Continue anyway?"):
+				if not keypress_confirm('Continue anyway?'):
 					sys.exit(1)
 			else:
-				msg(wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
-				sys.exit(2)
+				die(2,wmsg['addr_not_found'].format(pnm=pnm,mmgenaddr=mmaddr))
 		else:
-			msg(wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
-			sys.exit(2)
+			die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
 
 	return btcaddr
 
@@ -323,7 +317,7 @@ def make_b2m_map(inputs_data,tx_out,ail_w,ail_f):
 	d.update(ail_f.make_reverse_dict(tx_out.keys()))
 	return d
 
-cmd_args = opt.opts.init(opts_data)
+cmd_args = opts.init(opts_data)
 
 if opt.comment_file:
 	comment = get_tx_comment_from_file(opt.comment_file)
@@ -333,7 +327,7 @@ c = connect_to_bitcoind()
 if not opt.info:
 	do_license_msg(immed=True)
 
-	tx_out,change_addr = {},""
+	tx_out,change_addr = {},''
 
 	addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
 	cmd_args = set(cmd_args) - set(addrfiles)
@@ -347,131 +341,122 @@ if not opt.info:
 	ail_w = AddrInfoList(bitcoind_connection=c)
 
 	for a in cmd_args:
-		if "," in a:
-			a1,a2 = split2(a,",")
+		if ',' in a:
+			a1,a2 = split2(a,',')
 			if is_btc_addr(a1):
 				btcaddr = a1
 			elif is_mmgen_addr(a1):
 				btcaddr = mmaddr2btcaddr(c,a1,ail_w,ail_f)
 			else:
-				msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
-				sys.exit(2)
+				die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
 
 			ret = normalize_btc_amt(a2)
 			if ret:
 				tx_out[btcaddr] = ret
 			else:
-				msg("%s: invalid amount in argument '%s'" % (a2,a))
-				sys.exit(2)
+				die(2,"%s: invalid amount in argument '%s'" % (a2,a))
 		elif is_mmgen_addr(a) or is_btc_addr(a):
 			if change_addr:
-				msg("ERROR: More than one change address specified: %s, %s" %
+				die(2,'ERROR: More than one change address specified: %s, %s' %
 						(change_addr, a))
-				sys.exit(2)
-			change_addr = a if is_btc_addr(a) else \
-							mmaddr2btcaddr(c,a,ail_w,ail_f)
+			change_addr = a if is_btc_addr(a) else mmaddr2btcaddr(c,a,ail_w,ail_f)
 			tx_out[change_addr] = 0
 		else:
-			msg("%s: unrecognized argument" % a)
-			sys.exit(2)
+			die(2,'%s: unrecognized argument' % a)
 
 	if not tx_out:
-		msg("At least one output must be specified on the command line")
-		sys.exit(2)
+		die(2,'At least one output must be specified on the command line')
 
-	tx_fee = opt.tx_fee if opt.tx_fee else g.tx_fee
+	tx_fee = (g.tx_fee,opt.tx_fee)[bool(opt.tx_fee)]
 	tx_fee = normalize_btc_amt(tx_fee)
 	if tx_fee > g.max_tx_fee:
-		msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
-		sys.exit(2)
+		die(2,'Transaction fee too large: %s > %s' % (tx_fee,g.max_tx_fee))
 
 if g.bogus_wallet_data:  # for debugging purposes only
-	import mmgen.rpc.data
 	us = eval(get_data_from_file(g.bogus_wallet_data))
 else:
 	us = c.listunspent()
-#	write_data_to_file("bogus_unspent.json", repr(us), "bogus unspent data")
+#	write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
 #	sys.exit()
 
-if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2)
+if not us:
+	die(2,wmsg['no_spendable_outputs'])
 for o in us:
-	o.mmid,o.comment = parse_mmgen_label(o.account)
-	del o.account
+	o['mmid'],o['comment'] = parse_mmgen_label(o['account'])
+	del o['account']
 unspent = sort_and_view(us)
 
-total = trim_exponent(sum([i.amount for i in unspent]))
+total = trim_exponent(sum([i['amount'] for i in unspent]))
 
-msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
-if opt.info: sys.exit(0)
+msg('Total unspent: %s BTC (%s outputs)' % (total, len(unspent)))
+if opt.info: sys.exit()
 
 send_amt = sum([tx_out[i] for i in tx_out.keys()])
-msg("Total amount to spend: %s%s" % (
-		(send_amt or "Unknown")," BTC" if send_amt else ""))
+msg('Total amount to spend: %s' % ('%s BTC'%send_amt,'Unknown')[bool(send_amt)])
 
 while True:
 	sel_nums = select_outputs(unspent,
-			"Enter a range or space-separated list of outputs to spend: ")
-	msg("Selected output%s: %s" %
-		(("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums))
-	)
+			'Enter a range or space-separated list of outputs to spend: ')
+	msg('Selected output%s: %s' % (
+			('s','')[len(sel_nums)==1],
+			' '.join(str(i) for i in sel_nums)
+		))
 	sel_unspent = [unspent[i-1] for i in sel_nums]
 
-	mmaddrs = set([i.mmid for i in sel_unspent])
-	mmaddrs.discard("")
+	mmaddrs = set([i['mmid'] for i in sel_unspent])
+	mmaddrs.discard('')
 
 	if mmaddrs and len(mmaddrs) < len(sel_unspent):
-		msg(wmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
-		if not keypress_confirm("Accept?"):
+		msg(wmsg['mixed_inputs'] % ', '.join(sorted(mmaddrs)))
+		if not keypress_confirm('Accept?'):
 			continue
 
-	total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
+	total_in = trim_exponent(sum([i['amount'] for i in sel_unspent]))
 	change   = trim_exponent(total_in - (send_amt + tx_fee))
 
 	if change >= 0:
-		prompt = "Transaction produces %s BTC in change.  OK?" % change
+		prompt = 'Transaction produces %s BTC in change.  OK?' % change
 		if keypress_confirm(prompt,default_yes=True):
 			break
 	else:
 		msg(wmsg['not_enough_btc'] % change)
 
 if change > 0 and not change_addr:
-	msg(wmsg['throwaway_change'] % change)
-	sys.exit(2)
+	die(2,wmsg['throwaway_change'] % change)
 
 if change_addr in tx_out and not change:
-	msg("Warning: Change address will be unused as transaction produces no change")
+	msg('Warning: Change address will be unused as transaction produces no change')
 	del tx_out[change_addr]
 
 for k,v in tx_out.items(): tx_out[k] = float(v)
 
 if change > 0: tx_out[change_addr] = float(change)
 
-tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
+tx_in = [{'txid':i['txid'], 'vout':i['vout']} for i in sel_unspent]
 
-dmsg("tx_in:  %s\ntx_out: %s" % (repr(tx_in),repr(tx_out)))
+dmsg('tx_in:  %s\ntx_out: %s' % (repr(tx_in),repr(tx_out)))
 
 if opt.comment_file:
-	if keypress_confirm("Edit comment?",False):
+	if keypress_confirm('Edit comment?',False):
 		comment = get_tx_comment_from_user(comment)
 else:
-	if keypress_confirm("Add a comment to transaction?",False):
+	if keypress_confirm('Add a comment to transaction?',False):
 		comment = get_tx_comment_from_user()
 	else: comment = False
 
 tx_hex = c.createrawtransaction(tx_in,tx_out)
-qmsg("Transaction successfully created")
+qmsg('Transaction successfully created')
 
 amt = send_amt or change
 tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
 metadata = tx_id, amt, make_timestamp()
-sel_unspent = [i.__dict__ for i in sel_unspent]
 
 b2m_map = make_b2m_map(sel_unspent,tx_out,ail_w,ail_f)
 
-prompt_and_view_tx_data(c,"View decoded transaction?",
+prompt_and_view_tx_data(c,'View decoded transaction?',
 		sel_unspent,tx_hex,b2m_map,comment,metadata)
 
-outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
-data = make_tx_data("{} {} {}".format(*metadata),
+outfile = 'tx_%s[%s].%s' % (tx_id,amt,g.rawtx_ext)
+data = make_tx_data('{} {} {}'.format(*metadata),
 			tx_hex,sel_unspent,b2m_map,comment)
-write_data_to_file(outfile,data,"transaction",ask_write_default_yes=False)
+write_data_to_file(outfile,data,'transaction',ask_write_default_yes=False)

+ 19 - 24
mmgen/main_txsend.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,17 +20,13 @@
 mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
 """
 
-import sys
-
-import mmgen.globalvars as g
-import mmgen.opt as opt
+from mmgen.common import *
 from mmgen.tx import *
-from mmgen.util import *
 
 opts_data = {
-	'desc':    "Send a Bitcoin transaction signed by {pnm}-txsign".format(
+	'desc':    'Send a Bitcoin transaction signed by {pnm}-txsign'.format(
 					pnm=g.proj_name.lower()),
-	'usage':   "[opts] <signed transaction file>",
+	'usage':   '[opts] <signed transaction file>',
 	'options': """
 -h, --help      Print this help message
 -d, --outdir= d Specify an alternate directory 'd' for output
@@ -38,17 +34,17 @@ opts_data = {
 """
 }
 
-cmd_args = opt.opts.init(opts_data)
+cmd_args = opts.init(opts_data)
 
 if len(cmd_args) == 1:
 	infile = cmd_args[0]; check_infile(infile)
-else: opt.opts.usage()
+else: opts.usage()
 
 # Begin execution
 
 do_license_msg()
 
-tx_data = get_lines_from_file(infile,"signed transaction data")
+tx_data = get_lines_from_file(infile,'signed transaction data')
 
 metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
 
@@ -56,32 +52,31 @@ qmsg("Signed transaction file '%s' is valid" % infile)
 
 c = connect_to_bitcoind()
 
-prompt_and_view_tx_data(c,"View transaction data?",
+prompt_and_view_tx_data(c,'View transaction data?',
 	inputs_data,tx_hex,b2m_map,comment,metadata)
 
-if keypress_confirm("Edit transaction comment?"):
+if keypress_confirm('Edit transaction comment?'):
 	comment = get_tx_comment_from_user(comment)
-	data = make_tx_data("{} {} {}".format(*metadata), tx_hex,
+	data = make_tx_data('{} {} {}'.format(*metadata), tx_hex,
 				inputs_data, b2m_map, comment)
-	write_data_to_file(infile,data,"signed transaction with edited comment")
+	write_data_to_file(infile,data,'signed transaction with edited comment')
 
 warn   = "Once this transaction is sent, there's no taking it back!"
-action = "broadcast this transaction to the network"
-expect =  "YES, I REALLY WANT TO DO THIS"
+action = 'broadcast this transaction to the network'
+expect =  'YES, I REALLY WANT TO DO THIS'
 
-if opt.quiet: warn,expect = "","YES"
+if opt.quiet: warn,expect = '','YES'
 
 confirm_or_exit(warn, action, expect)
 
-msg("Sending transaction")
+msg('Sending transaction')
 
 try:
 	tx_id = c.sendrawtransaction(tx_hex)
 except:
-	msg("Unable to send transaction")
-	sys.exit(3)
+	die(3,'Unable to send transaction')
 
-msg("Transaction sent: %s" % tx_id)
+msg('Transaction sent: %s' % tx_id)
 
-of = "tx_{}[{}].txid".format(*metadata[:2])
-write_data_to_file(of, tx_id+"\n","transaction ID",ask_overwrite=True)
+of = 'tx_{}[{}].txid'.format(*metadata[:2])
+write_data_to_file(of, tx_id+'\n','transaction ID',ask_overwrite=True)

+ 76 - 83
mmgen/main_txsign.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,21 +20,16 @@
 mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
 """
 
-import sys
-
-import mmgen.globalvars as g
-import mmgen.opt as opt
+from mmgen.common import *
 from mmgen.tx import *
-from mmgen.util import do_license_msg,dmsg
 from mmgen.seed import SeedSource
 
-
 pnm = g.proj_name
 pnl = pnm.lower()
 
 opts_data = {
-	'desc':    "Sign Bitcoin transactions generated by {pnl}-txcreate".format(pnl=pnl),
-	'usage':   "[opts] <transaction file>... [seed source]...",
+	'desc':    'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnl),
+	'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-
@@ -101,7 +96,7 @@ Seed data supplied in files must have the following extensions:
 FMT CODES:
   {f}
 """.format(
-		f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
+		f='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
 		g=g,pnm=pnm,pnl=pnl
 	)
 }
@@ -126,12 +121,11 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
 		if infiles:
 			ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
 		elif opt.in_fmt:
-			qmsg("Need seed data for Seed ID %s" % seed_id)
+			qmsg('Need seed data for Seed ID %s' % seed_id)
 			ss = SeedSource()
-			msg("User input produced Seed ID %s" % make_chksum_8(seed))
+			msg('User input produced Seed ID %s' % make_chksum_8(seed))
 		else:
-			msg("ERROR: No seed source found for Seed ID: %s" % seed_id)
-			sys.exit(2)
+			die(2,'ERROR: No seed source found for Seed ID: %s' % seed_id)
 
 		saved_seeds[ss.seed.sid] = ss.seed.data
 
@@ -141,7 +135,7 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
 def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
 
 	seed_ids = set([i[:8] for i in mmgen_addrs])
-	vmsg("Need seed%s: %s" % (suf(seed_ids,"k")," ".join(seed_ids)))
+	vmsg('Need seed%s: %s' % (suf(seed_ids,'k'),' '.join(seed_ids)))
 	d = []
 
 	from mmgen.addr import generate_addrs
@@ -149,25 +143,25 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
 		# Returns only if seed is found
 		seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds)
 		addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
-		opt.gen_what = "ka"
-		ai = generate_addrs(seed,addr_nums,source="txsign")
-		d += [("{}:{}".format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata]
+		opt.gen_what = 'ka'
+		ai = generate_addrs(seed,addr_nums,source='txsign')
+		d += [('{}:{}'.format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata]
 	return d
 
 
 def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None):
 
 	if keys:
-		qmsg("Passing %s key%s to bitcoind" % (len(keys),suf(keys,"k")))
-		dmsg("Keys:\n  %s" % "\n  ".join(keys))
+		qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
+		dmsg('Keys:\n  %s' % '\n  '.join(keys))
 
-	msg_r("Signing transaction{}...".format(tx_num_str))
-	from mmgen.rpc import exceptions
+	msg_r('Signing transaction{}...'.format(tx_num_str))
+#	from mmgen.rpc import exceptions
 	try:
 		sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
-	except exceptions.InvalidAddressOrKey:
-		msg("failed\nInvalid address or key")
-		sys.exit(3)
+#	except exceptions.InvalidAddressOrKey:
+	except: # TODO
+		die(3,'failed\nInvalid address or key')
 
 	return sig_tx
 
@@ -177,26 +171,27 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys):
 	try:
 		sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
 	except:
-		from mmgen.rpc import exceptions
-		msg("Using keys in wallet.dat as per user request")
-		prompt = "Enter passphrase for bitcoind wallet: "
+#		from mmgen.rpc import exceptions
+		msg('Using keys in wallet.dat as per user request')
+		prompt = 'Enter passphrase for bitcoind wallet: '
 		while True:
 			passwd = get_bitcoind_passphrase(prompt)
 
 			try:
 				c.walletpassphrase(passwd, 9999)
-			except exceptions.WalletPassphraseIncorrect:
-				msg("Passphrase incorrect")
+#			except exceptions.WalletPassphraseIncorrect:
+			except: # TODO
+				msg('Passphrase incorrect (or some other error)')
 			else:
-				msg("Passphrase OK"); break
+				msg('Passphrase OK'); break
 
 		sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
 
-		msg("Locking wallet")
+		msg('Locking wallet')
 		try:
 			c.walletlock()
 		except:
-			msg("Failed to lock wallet")
+			msg('Failed to lock wallet')
 
 	return sig_tx
 
@@ -204,55 +199,57 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys):
 def check_maps_from_seeds(maplist,desc,infiles,saved_seeds,return_keys=False):
 
 	if not maplist: return []
-	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from seed(s))".format(
+	qmsg('Checking {pnm} -> BTC address mappings for {w}s (from seed(s))'.format(
 				pnm=pnm,w=desc))
 	d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds)
 #	0=mmaddr 1=addr 2=wif
 	m = dict([(e[0],e[1]) for e in d])
 	for a,b in zip(sorted(m),sorted(maplist)):
 		if a != b:
-			al,bl = "generated seed:","tx file:"
-			msg(wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b]))
-			sys.exit(3)
+			al,bl = 'generated seed:','tx file:'
+			die(3,wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b]))
 	if return_keys:
 		ret = [e[2] for e in d]
-		vmsg("Added %s wif key%s from seeds" % (len(ret),suf(ret,"k")))
+		vmsg('Added %s wif key%s from seeds' % (len(ret),suf(ret,'k')))
 		return ret
 
 def missing_keys_errormsg(addrs):
 	Msg("""
 A key file must be supplied (or use the '--use-wallet-dat' option)
 for the following non-{pnm} address{suf}:\n    {l}""".format(
-	pnm=pnm, suf=suf(addrs,"a"), l="\n    ".join(addrs)).strip())
+	pnm=pnm, suf=suf(addrs,'a'), l='\n    '.join(addrs)).strip())
 
 
 def parse_mmgen_keyaddr_file():
 	from mmgen.addr import AddrInfo
 	ai = AddrInfo(opt.mmgen_keys_from_file,has_keys=True)
-	vmsg("Found %s wif key%s for Seed ID %s" %
-			(ai.num_addrs, suf(ai.num_addrs,"k"), ai.seed_id))
+	vmsg('Found %s wif key%s for Seed ID %s' %
+			(ai.num_addrs, suf(ai.num_addrs,'k'), ai.seed_id))
 	# idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif)
 	return dict(
-		[("%s:%s"%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata])
+		[('%s:%s'%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata])
 
 
 def parse_keylist(from_file):
 	fn = opt.keys_from_file
 	from mmgen.crypto import mmgen_decrypt_file_maybe
-	dec = mmgen_decrypt_file_maybe(fn,"non-{} keylist file".format(pnm))
-	# Remove possible dups from key-address file
-	keys_all = set(remove_comments(dec.splitlines())) # DOS-safe
+	dec = mmgen_decrypt_file_maybe(fn,'non-{} keylist file'.format(pnm))
+	keys_all = remove_comments(dec.splitlines()) # DOS-safe
+	# Key list could be bitcoind dump, so remove first space and everything following
+	keys_all = [k.split()[0] for k in keys_all]
+	keys_all = set(keys_all) # Remove possible dups
+	dmsg(repr(keys_all))
 	d = from_file['mmdata']
 	kawifs = [d[k][1] for k in d.keys()]
 	keys = [k for k in keys_all if k not in kawifs]
 	removed = len(keys_all) - len(keys)
-	if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,"k")))
+	if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,'k')))
 	addrs = []
 	wif2addr_f = get_wif2addr_f()
 	for n,k in enumerate(keys,1):
-		qmsg_r("\rGenerating addresses from keylist: %s/%s" % (n,len(keys)))
+		qmsg_r('\rGenerating addresses from keylist: %s/%s' % (n,len(keys)))
 		addrs.append(wif2addr_f(k))
-	qmsg("\rGenerated addresses from keylist: %s/%s " % (n,len(keys)))
+	qmsg('\rGenerated addresses from keylist: %s/%s ' % (n,len(keys)))
 
 	return dict(zip(addrs,keys))
 
@@ -260,7 +257,7 @@ def parse_keylist(from_file):
 # Check inputs and outputs maps against key-address file, deleting entries:
 def check_maps_from_kafile(imap,desc,kadata,return_keys=False):
 	if not kadata: return []
-	qmsg("Checking {pnm} -> BTC address mappings for {w}s (from key-address file)".format(pnm=pnm,w=desc))
+	qmsg('Checking {pnm} -> BTC address mappings for {w}s (from key-address file)'.format(pnm=pnm,w=desc))
 	ret = []
 	for k in imap.keys():
 		if k in kadata.keys():
@@ -268,12 +265,11 @@ def check_maps_from_kafile(imap,desc,kadata,return_keys=False):
 				del imap[k]
 				ret += [kadata[k][1]]
 			else:
-				kl,il = "key-address file:","tx file:"
-				msg(wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k]))
-				sys.exit(2)
-	if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),desc))
+				kl,il = 'key-address file:','tx file:'
+				die(2,wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k]))
+	if ret: vmsg('Removed %s address%s from %ss map' % (len(ret),suf(ret,'a'),desc))
 	if return_keys:
-		vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),desc))
+		vmsg('Added %s wif key%s from %ss map' % (len(ret),suf(ret,'k'),desc))
 		return ret
 
 
@@ -283,16 +279,16 @@ def get_keys_from_keylist(kldata,other_addrs):
 		if addr in kldata.keys():
 			ret += [kldata[addr]]
 			other_addrs.remove(addr)
-	vmsg("Added %s wif key%s from user-supplied keylist" %
-			(len(ret),suf(ret,"k")))
+	vmsg('Added %s wif key%s from user-supplied keylist' %
+			(len(ret),suf(ret,'k')))
 	return ret
 
 
-infiles = opt.opts.init(opts_data,add_opts=["b16"])
+infiles = opts.init(opts_data,add_opts=['b16'])
 
 #if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
 
-if not infiles: opt.opts.usage()
+if not infiles: opts.usage()
 for i in infiles: check_infile(i)
 
 c = connect_to_bitcoind()
@@ -310,28 +306,26 @@ if opt.mmgen_keys_from_file:
 if opt.keys_from_file:
 	from_file['kldata'] = parse_keylist(from_file) or {}
 
-tx_num_str = ""
+tx_num_str = ''
 for tx_num,tx_file in enumerate(tx_files,1):
 	if len(tx_files) > 1:
-		msg("\nTransaction #%s of %s:" % (tx_num,len(tx_files)))
-		tx_num_str = " #%s" % tx_num
+		msg('\nTransaction #%s of %s:' % (tx_num,len(tx_files)))
+		tx_num_str = ' #%s' % tx_num
 
-	m = "" if opt.tx_id else "transaction data"
+	m = ('transaction data','')[bool(opt.tx_id)]
 	tx_data = get_lines_from_file(tx_file,m)
 
 	metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,tx_file)
 	vmsg("Successfully opened transaction file '%s'" % tx_file)
 
-	if opt.tx_id:
-		msg(metadata[0])
-		sys.exit(0)
+	if opt.tx_id: die(0,metadata[0])
 
 	if opt.info or opt.terse_info:
 		view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pause=False,
 				terse=opt.terse_info)
-		sys.exit(0)
+		sys.exit()
 
-	prompt_and_view_tx_data(c,"View data for transaction{}?".format(tx_num_str),
+	prompt_and_view_tx_data(c,'View data for transaction{}?'.format(tx_num_str),
 		inputs_data,tx_hex,b2m_map,comment,metadata)
 
 	# Start
@@ -347,20 +341,20 @@ for tx_num,tx_file in enumerate(tx_files,1):
 	omap = dict([(j[0],i) for i,j in b2m_map.items()])
 	sids = set([i[:8] for i in imap.keys()])
 
-	keys += check_maps_from_kafile(imap,"input",from_file['mmdata'],True)
-	check_maps_from_kafile(omap,"output",from_file['mmdata'])
+	keys += check_maps_from_kafile(imap,'input',from_file['mmdata'],True)
+	check_maps_from_kafile(omap,'output',from_file['mmdata'])
 
-	keys += check_maps_from_seeds(imap,"input",seed_files,saved_seeds,True)
-	check_maps_from_seeds(omap,"output",seed_files,saved_seeds)
+	keys += check_maps_from_seeds(imap,'input',seed_files,saved_seeds,True)
+	check_maps_from_seeds(omap,'output',seed_files,saved_seeds)
 
 	extra_sids = set(saved_seeds.keys()) - sids
 	if extra_sids:
-		msg("Unused Seed ID%s: %s" %
-			(suf(extra_sids,"k")," ".join(extra_sids)))
+		msg('Unused Seed ID%s: %s' %
+			(suf(extra_sids,'k'),' '.join(extra_sids)))
 
 	# Begin signing
 	sig_data = [
-		{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
+		{'txid':i['txid'],'vout':i['vout'],'scriptPubKey':i['scriptPubKey']}
 			for i in inputs_data]
 
 	if opt.use_wallet_dat:
@@ -370,21 +364,20 @@ for tx_num,tx_file in enumerate(tx_files,1):
 		sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
 
 	if sig_tx['complete']:
-		msg("OK")
-		if keypress_confirm("Edit transaction comment?"):
+		msg('OK')
+		if keypress_confirm('Edit transaction comment?'):
 			comment = get_tx_comment_from_user(comment)
-		outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
+		outfile = 'tx_%s[%s].%s' % (metadata[0],metadata[1],g.sigtx_ext)
 		data = make_tx_data(
-				"{} {} {t}".format(*metadata[:2],
+				'{} {} {t}'.format(*metadata[:2],
 				t=make_timestamp()),
 				sig_tx['hex'], inputs_data, b2m_map, comment
 			)
 		write_data_to_file(
 			outfile,data,
-			"signed transaction{}".format(tx_num_str),
-			ask_write_prompt="Save signed transaction?"
+			'signed transaction{}'.format(tx_num_str),
+			ask_write_prompt='Save signed transaction?'
 		)
 	else:
-		msg_r("failed\nSome keys were missing.  ")
-		msg("Transaction %scould not be signed." % tx_num_str)
-		sys.exit(3)
+		msg_r('failed\nSome keys were missing.  ')
+		die(3,'Transaction %scould not be signed.' % tx_num_str)

+ 41 - 42
mmgen/main_wallet.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,40 +20,39 @@
 mmgen/main_wallet:  Entry point for MMGen wallet-related scripts
 """
 
-import sys,os,re
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import die,msg,green,do_license_msg,check_infile,mdie,mmsg,qmsg,capfirst
+import os,re
+
+from mmgen.common import *
 from mmgen.seed import SeedSource
 
 bn = os.path.basename(sys.argv[0])
-invoked_as = re.sub(r'^wallet','',bn.split("-")[-1])
+invoked_as = re.sub(r'^wallet','',bn.split('-')[-1])
 
-usage = "[opts] [infile]"
+usage = '[opts] [infile]'
 nargs = 1
-iaction = "convert"
-oaction = "convert"
-bw_note = opt.opts.bw_note
-pw_note = opt.opts.pw_note
-
-if invoked_as == "gen":
-	desc = "Generate an {pnm} wallet from a random seed"
-	opt_filter = "ehdoJlLpPqrSvz"
-	usage = "[opts]"
-	oaction = "output"
+iaction = 'convert'
+oaction = 'convert'
+bw_note = opts.bw_note
+pw_note = opts.pw_note
+
+if invoked_as == 'gen':
+	desc = 'Generate an {pnm} wallet from a random seed'
+	opt_filter = 'ehdoJlLpPqrSvz'
+	usage = '[opts]'
+	oaction = 'output'
 	nargs = 0
-elif invoked_as == "conv":
-	desc = "Convert an {pnm} wallet from one format to another"
+elif invoked_as == 'conv':
+	desc = 'Convert an {pnm} wallet from one format to another'
 	opt_filter = None
-elif invoked_as == "chk":
-	desc = "Check validity of an {pnm} wallet"
-	opt_filter = "ehiHOlpPqrvz"
-	iaction = "input"
-elif invoked_as == "passchg":
-	desc = "Change the password, hash preset or label of an {pnm} wallet"
-	opt_filter = "ehdiHkKOlLmpPqrSvz"
-	iaction = "input"
-	bw_note = ""
+elif invoked_as == 'chk':
+	desc = 'Check validity of an {pnm} wallet'
+	opt_filter = 'ehiHOlpPqrvz'
+	iaction = 'input'
+elif invoked_as == 'passchg':
+	desc = 'Change the password, hash preset or label of an {pnm} wallet'
+	opt_filter = 'ehdiHkKOlLmpPqrSvz'
+	iaction = 'input'
+	bw_note = ''
 else:
 	die(1,"'%s': unrecognized invocation" % bn)
 
@@ -103,36 +102,36 @@ opts_data = {
 FMT CODES:
   {f}
 """.format(
-	f="\n  ".join(SeedSource.format_fmt_codes().splitlines()),
+	f='\n  '.join(SeedSource.format_fmt_codes().splitlines()),
 	pw_note=pw_note,
-	bw_note=("","\n\n" + bw_note)[int(bool(bw_note))]
+	bw_note=('','\n\n' + bw_note)[bool(bw_note)]
 	)
 }
 
-cmd_args = opt.opts.init(opts_data,opt_filter=opt_filter)
+cmd_args = opts.init(opts_data,opt_filter=opt_filter)
 
 if len(cmd_args) < nargs \
 		and not opt.hidden_incog_input_params and not opt.in_fmt:
-	die(1,"An input file or input format must be specified")
+	die(1,'An input file or input format must be specified')
 elif len(cmd_args) > nargs \
 		or (len(cmd_args) == nargs and opt.hidden_incog_input_params):
-	msg("No input files may be specified" if invoked_as == "gen"
-			else "Too many input files specified")
-	opt.opts.usage()
+	msg('No input files may be specified' if invoked_as == 'gen'
+			else 'Too many input files specified')
+	opts.usage()
 
 if cmd_args: check_infile(cmd_args[0])
 
-if not invoked_as == "chk": do_license_msg()
+if not invoked_as == 'chk': do_license_msg()
 
-if invoked_as in ("conv","passchg"): msg(green("Processing input wallet"))
+if invoked_as in ('conv','passchg'): msg(green('Processing input wallet'))
 
-ss_in = None if invoked_as == "gen" \
-			else SeedSource(*cmd_args,passchg=invoked_as=="passchg")
+ss_in = None if invoked_as == 'gen' \
+			else SeedSource(*cmd_args,passchg=invoked_as=='passchg')
 
-if invoked_as == "chk":
+if invoked_as == 'chk':
 	sys.exit()
 
-if invoked_as in ("conv","passchg"): msg(green("Processing output wallet"))
+if invoked_as in ('conv','passchg'): msg(green('Processing output wallet'))
 
-ss_out = SeedSource(ss=ss_in,passchg=invoked_as=="passchg")
+ss_out = SeedSource(ss=ss_in,passchg=invoked_as=='passchg')
 ss_out.write_to_file()

+ 1 - 1
mmgen/mn_electrum.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 1 - 1
mmgen/mn_tirosh.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 19 - 20
mmgen/obj.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
 obj.py:  The MMGenObject class and methods
 """
 import mmgen.globalvars as g
-from mmgen.util import mdie,mmsg
 
 lvl = 0
 
@@ -29,16 +28,16 @@ class MMGenObject(object):
 	# Pretty-print any object of type MMGenObject, recursing into sub-objects
 	def __str__(self):
 		global lvl
-		indent = lvl * "    "
+		indent = lvl * '    '
 
 		def fix_linebreaks(v,fixed_indent=None):
-			if "\n" in v:
-				i = indent+"    " if fixed_indent == None else fixed_indent*" "
-				return "\n"+i + v.replace("\n","\n"+i)
+			if '\n' in v:
+				i = indent+'    ' if fixed_indent == None else fixed_indent*' '
+				return '\n'+i + v.replace('\n','\n'+i)
 			else: return repr(v)
 
 		def conv(v,col_w):
-			vret = ""
+			vret = ''
 			if type(v) == str:
 				if not (set(list(v)) <= set(list(g.printable))):
 					vret = repr(v)
@@ -47,30 +46,30 @@ class MMGenObject(object):
 			elif type(v) == int or type(v) == long:
 				vret = str(v)
 			elif type(v) == dict:
-				sep = "\n{}{}".format(indent," "*4)
+				sep = '\n{}{}'.format(indent,' '*4)
 				cw = max(len(k) for k in v) + 2
-				t = sep.join(["{:<{w}}: {}".format(
+				t = sep.join(['{:<{w}}: {}'.format(
 					repr(k),
 	(fix_linebreaks(v[k],fixed_indent=0) if type(v[k]) == str else v[k]),
 					w=cw)
 				for k in sorted(v)])
-				vret = "{" + sep + t + "\n" + indent + "}"
+				vret = '{' + sep + t + '\n' + indent + '}'
 			elif type(v) in (list,tuple):
-				sep = "\n{}{}".format(indent," "*4)
-				t = " ".join([repr(e) for e in sorted(v)])
-				o,c = ("[","]") if type(v) == list else ("(",")")
-				vret = o + sep + t + "\n" + indent + c
+				sep = '\n{}{}'.format(indent,' '*4)
+				t = ' '.join([repr(e) for e in sorted(v)])
+				o,c = (('(',')'),('[',']'))[type(v)==list]
+				vret = o + sep + t + '\n' + indent + c
 			elif repr(v)[:14] == '<bound method ':
-				vret = " ".join(repr(v).split()[0:3]) + ">"
+				vret = ' '.join(repr(v).split()[0:3]) + '>'
 #				vret = repr(v)
 
 			return vret or type(v)
 
 		out = []
-		def f(k): return k[:2] != "__"
+		def f(k): return k[:2] != '__'
 		keys = filter(f, dir(self))
 		col_w = max(len(k) for k in keys)
-		fs = "{}%-{}s: %s".format(indent,col_w)
+		fs = '{}%-{}s: %s'.format(indent,col_w)
 
 		methods = [k for k in keys if repr(getattr(self,k))[:14] == '<bound method ']
 
@@ -83,11 +82,11 @@ class MMGenObject(object):
 		for k in sorted(methods) + sorted(other) + sorted(objects):
 			val = getattr(self,k)
 			if str(type(val))[:13] == "<class 'mmgen": # recurse into sub-objects
-				out.append("\n%s%s (%s):" % (indent,k,type(val)))
+				out.append('\n%s%s (%s):' % (indent,k,type(val)))
 				lvl += 1
-				out.append(str(getattr(self,k))+"\n")
+				out.append(str(getattr(self,k))+'\n')
 				lvl -= 1
 			else:
 				out.append(fs % (k, conv(val,col_w)))
 
-		return repr(self) + "\n    " + "\n    ".join(out)
+		return repr(self) + '\n    ' + '\n    '.join(out)

+ 65 - 71
mmgen/opts.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,10 +21,11 @@ opts.py:  MMGen-specific options processing after generic processing by share.Op
 """
 import sys
 
+class opt(object): pass
+
 import mmgen.globalvars as g
 import mmgen.share.Opts
-import opt
-from mmgen.util import msg,msg_r,mdie,mmsg,Msg,die,is_mmgen_wallet_label
+from mmgen.util import *
 
 pw_note = """
 For passphrases all combinations of whitespace are equal and leading and
@@ -41,7 +42,7 @@ seed, the same seed length and hash preset parameters must always be used.
 """.strip()
 
 def usage():
-	Msg("USAGE: %s %s" % (g.prog_name, usage_txt))
+	Msg('USAGE: %s %s' % (g.prog_name, usage_txt))
 	sys.exit(2)
 
 def print_version_info():
@@ -55,7 +56,7 @@ def die_on_incompatible_opts(incompat_list):
 	for group in incompat_list:
 		bad = [k for k in opt.__dict__ if opt.__dict__[k] and k in group]
 		if len(bad) > 1:
-			die(1,"Conflicting options: %s" % ", ".join([fmt_opt(b) for b in bad]))
+			die(1,'Conflicting options: %s' % ', '.join([fmt_opt(b) for b in bad]))
 
 def _typeconvert_from_dfl(key):
 
@@ -66,7 +67,7 @@ def _typeconvert_from_dfl(key):
 	gtype = type(gval)
 
 	try:
-		opt.__dict__[key] = gtype(opt.__dict__[key])
+		setattr(opt,key,gtype(uval))
 	except:
 		d = {
 			'int':   'an integer',
@@ -75,44 +76,45 @@ def _typeconvert_from_dfl(key):
 			'bool':  'a boolean value',
 		}
 		die(1, "'%s': invalid parameter for '--%s' option (not %s)" % (
-			opt.__dict__[key],
-			key.replace("_","-"),
+			uval,
+			key.replace('_','-'),
 			d[gtype.__name__]
 		))
 
 	if g.debug:
-		Msg("Opt overriden by user:\n    %-18s: %s" % (
-				key, ("%s -> %s" % (gval,uval))
+		Msg('Opt overriden by user:\n    %-18s: %s' % (
+				key, ('%s -> %s' % (gval,uval))
 			))
 
-def fmt_opt(o): return "--" + o.replace("_","-")
+def fmt_opt(o): return '--' + o.replace('_','-')
 
 def _show_hash_presets():
-	fs = "  {:<7} {:<6} {:<3}  {}"
-	msg("Available parameters for scrypt.hash():")
-	msg(fs.format("Preset","N","r","p"))
+	fs = '  {:<7} {:<6} {:<3}  {}'
+	msg('Available parameters for scrypt.hash():')
+	msg(fs.format('Preset','N','r','p'))
 	for i in sorted(g.hash_presets.keys()):
 		msg(fs.format("'%s'" % i, *g.hash_presets[i]))
-	msg("N = memory usage (power of two), p = iterations (rounds)")
+	msg('N = memory usage (power of two), p = iterations (rounds)')
 
 def init(opts_data,add_opts=[],opt_filter=None):
 
 	if len(sys.argv) == 2 and sys.argv[1] == '--version':
-		print_version_info(); sys.exit()
+		print_version_info()
+		sys.exit()
 
 	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 = (
-			("Short opts",         short_opts),
-			("Long opts",          long_opts),
-			("Skipped opts",       skipped_opts),
-			("User-selected opts", uopts),
-			("Cmd args",           args),
+			('Short opts',         short_opts),
+			('Long opts',          long_opts),
+			('Skipped opts',       skipped_opts),
+			('User-selected opts', uopts),
+			('Cmd args',           args),
 		)
-		Msg("\n### BEGIN OPTS.PY ###")
-		for e in d: Msg("{:<20}: {}".format(*e))
+		Msg('\n=== opts.py debug ===')
+		for e in d: Msg('    {:<20}: {}'.format(*e))
 
 	# Save this for usage()
 	global usage_txt
@@ -123,61 +125,53 @@ def init(opts_data,add_opts=[],opt_filter=None):
 	for k in 'prog_name','desc','usage','options','notes':
 		if k in opts_data: del opts_data[k]
 
-	# Remove all unneeded attributes from opt, our special global namespace
-	for k in dir(opt):
-		if k[:2] == "__": del opt.__dict__[k]
-
 	# Transfer uopts into opt, setting required opts to None if not set by user
-	for o in [s.rstrip("=") for s in long_opts] + \
+	for o in [s.rstrip('=') for s in long_opts] + \
 			g.required_opts + add_opts + skipped_opts:
-		opt.__dict__[o] = uopts[o] if o in uopts else None
+		setattr(opt,o,uopts[o] if o in uopts else None)
 
 	# A special case - do this here, before opt gets set from g.dfl_vars
 	if opt.usr_randchars: g.use_urandchars = True
 
 	# If user opt is set, convert its type based on value in mmgen.globalvars
 	# If unset, set it to default value in mmgen.globalvars (g):
-	opt.__dict__['set_by_user'] = []
+	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)
 			opt.set_by_user.append(k)
 		else:
-			opt.__dict__[k] = g.__dict__[k]
+			setattr(opt,k,g.__dict__[k])
 
 	# Check user-set opts without modifying them
-	if not check_opts(uopts): sys.exit(1)
+	if not check_opts(uopts):
+		sys.exit(1)
 
 	if opt.show_hash_presets:
-		_show_hash_presets(); sys.exit(0)
+		_show_hash_presets()
+		sys.exit()
 
 	if opt.debug: opt.verbose = True
 
 	if g.debug:
-		Msg("Opts after processing:")
-		for k in opt.__dict__:
-			v = opt.__dict__[k]
-			if v != None and k != "opts":
-				Msg("    %-18s: %-6s [%s]" % (k,v,type(v).__name__))
-		Msg("### END OPTS.PY ###\n")
+		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:')
+		for k in a:
+			v = getattr(opt,k)
+			Msg('        %-18s: %-6s [%s]' % (k,v,type(v).__name__))
+		Msg("    Opts set to 'None':")
+		Msg('        %s\n' % '\n        '.join(b))
 
 	die_on_incompatible_opts(g.incompatible_opts)
 
 	return args
 
-# save for debugging
-def show_all_opts():
-	msg("Processed options:")
-	d = opt.__dict__
-	for k in [o for o in d if o != "opts"]:
-		tstr = type(d[k]) if d[k] not in (None,False,True) else ""
-		msg("%-20s: %-8s %s" % (k, d[k], tstr))
 
 def check_opts(usr_opts):       # Returns false if any check fails
 
 	def opt_splits(val,sep,n,desc):
-		sepword = "comma" if sep == "," else (
-					"colon" if sep == ":" else ("'"+sep+"'"))
+		sepword = 'comma' if sep == ',' else 'colon' if sep == ':' else "'%s'" % sep
 		try: l = val.split(sep)
 		except:
 			msg("'%s': invalid %s (not %s-separated list)" % (val,desc,sepword))
@@ -189,10 +183,10 @@ def check_opts(usr_opts):       # Returns false if any check fails
 					(val,desc,n,sepword))
 			return False
 
-	def opt_compares(val,op,target,desc,what=""):
-		if what: what += " "
-		if not eval("%s %s %s" % (val, op, target)):
-			msg("%s: invalid %s (%snot %s %s)" % (val,desc,what,op,target))
+	def opt_compares(val,op,target,desc,what=''):
+		if what: what += ' '
+		if not eval('%s %s %s' % (val, op, target)):
+			msg('%s: invalid %s (%snot %s %s)' % (val,desc,what,op,target))
 			return False
 		return True
 
@@ -205,8 +199,8 @@ def check_opts(usr_opts):       # Returns false if any check fails
 
 	def opt_is_in_list(val,lst,desc):
 		if val not in lst:
-			q,sep = ("'","','") if type(lst[0]) == str else ("",",")
-			msg("{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}".format(
+			q,sep = (('',','),("'","','"))[type(lst[0]) == str]
+			msg('{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format(
 					v=val,w=desc,q=q,
 					o=sep.join([str(i) for i in sorted(lst)])
 				))
@@ -218,8 +212,8 @@ def check_opts(usr_opts):       # Returns false if any check fails
 				% (val,desc,fmt_opt(key)))
 		return False
 
-	def opt_display(key,val='',beg="For selected",end=":\n"):
-		s = "%s=%s" % (fmt_opt(key),val) if val else fmt_opt(key)
+	def opt_display(key,val='',beg='For selected',end=':\n'):
+		s = '%s=%s' % (fmt_opt(key),val) if val else fmt_opt(key)
 		msg_r("%s option '%s'%s" % (beg,s,end))
 
 	global opt
@@ -245,23 +239,23 @@ def check_opts(usr_opts):       # Returns false if any check fails
 			from mmgen.seed import SeedSource,IncogWallet,Brainwallet,IncogWalletHidden
 			sstype = SeedSource.fmt_code_to_sstype(val)
 			if not sstype:
-				return opt_unrecognized(key,val,"format code")
+				return opt_unrecognized(key,val,'format code')
 			if key == 'out_fmt':
 				p = 'hidden_incog_output_params'
 				if sstype == IncogWalletHidden and not getattr(opt,p):
-						die(1,"Hidden incog format output requested. You must supply"
+						die(1,'Hidden incog format output requested. You must supply'
 						+ " a file and offset with the '%s' option" % fmt_opt(p))
 				if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
-					opt_display(key,val,beg="Selected",end=" ")
-					opt_display('old_incog_fmt',beg="conflicts with",end=":\n")
-					die(1,"Export to old incog wallet format unsupported")
+					opt_display(key,val,beg='Selected',end=' ')
+					opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
+					die(1,'Export to old incog wallet format unsupported')
 				elif issubclass(sstype,Brainwallet):
-					die(1,"Output to brainwallet format unsupported")
+					die(1,'Output to brainwallet format unsupported')
 		elif key in ('hidden_incog_input_params','hidden_incog_output_params'):
-			a = val.split(",")
+			a = val.split(',')
 			if len(a) != 2:
 				opt_display(key,val)
-				msg("Option requires two comma-separated arguments")
+				msg('Option requires two comma-separated arguments')
 				return False
 			if not opt_is_int(a[1],desc): return False
 			if key == 'hidden_incog_input_params':
@@ -280,7 +274,7 @@ def check_opts(usr_opts):       # Returns false if any check fails
 				from mmgen.seed import IncogWalletHidden
 				if val2 and val2 not in IncogWalletHidden.fmt_codes:
 					die(1,
-						"Option conflict:\n  %s, with\n  %s=%s" % (
+						'Option conflict:\n  %s, with\n  %s=%s' % (
 						fmt_opt(key),fmt_opt(key2),val2
 					))
 		elif key == 'seed_len':
@@ -289,21 +283,21 @@ def check_opts(usr_opts):       # Returns false if any check fails
 		elif key == 'hash_preset':
 			if not opt_is_in_list(val,g.hash_presets.keys(),desc): return False
 		elif key == 'brain_params':
-			a = val.split(",")
+			a = val.split(',')
 			if len(a) != 2:
 				opt_display(key,val)
-				msg("Option requires two comma-separated arguments")
+				msg('Option requires two comma-separated arguments')
 				return False
-			d = "seed length " + desc
+			d = 'seed length ' + desc
 			if not opt_is_int(a[0],d): return False
 			if not opt_is_in_list(int(a[0]),g.seed_lens,d): return False
-			d = "hash preset " + desc
+			d = 'hash preset ' + desc
 			if not opt_is_in_list(a[1],g.hash_presets.keys(),d): return False
 		elif key == 'usr_randchars':
 			if val == 0: continue
 			if not opt_is_int(val,desc): return False
-			if not opt_compares(val,">=",g.min_urandchars,desc): return False
-			if not opt_compares(val,"<=",g.max_urandchars,desc): return False
+			if not opt_compares(val,'>=',g.min_urandchars,desc): return False
+			if not opt_compares(val,'<=',g.max_urandchars,desc): return False
 		else:
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 

+ 128 - 0
mmgen/rpc.py

@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+rpc.py:  Bitcoin RPC library for the MMGen suite
+"""
+
+import httplib,base64,json,decimal
+
+from mmgen.common import *
+
+class BitcoinRPCConnection(object):
+
+	def __init__(
+				self,
+				host='localhost',port=8332,
+				user=None,passwd=None,auth_cookie=None,
+			):
+
+		if auth_cookie:
+			self.auth_str = auth_cookie
+		elif user and passwd:
+			self.auth_str = '{}:{}'.format(user,passwd)
+		else:
+			msg('Error: no Bitcoin RPC authentication method found')
+			if passwd: die(1,"'rpcuser' entry missing in bitcoin.conf")
+			elif user: die(1,"'rpcpassword' entry missing in bitcoin.conf")
+			else:
+				m1 = 'Either provide rpcuser/rpcpassword in bitcoin.conf'
+				m2 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir'
+				m3 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name)
+				die(1,'\n'.join((m1,m2,m3)))
+
+		self.host = host
+		self.port = port
+
+	# Normal mode: call with arg list unrolled, exactly as with 'bitcoin-cli'
+	# Batch mode:  call with list of arg lists as first argument
+	# kwargs are for local use and are not passed to server
+	def request(self,cmd,*args,**kwargs):
+
+		cf = { 'timeout': g.http_timeout, 'batch': False }
+
+		for k in cf:
+			if k in kwargs and kwargs[k]: cf[k] = kwargs[k]
+
+		c = httplib.HTTPConnection(self.host, self.port, False, cf['timeout'])
+
+		if cf['batch']:
+			p = [{'method':cmd,'params':r,'id':n} for n,r in enumerate(args[0],1)]
+		else:
+			p = {'method':cmd,'params':args,'id':1}
+
+		dmsg('=== rpc.py debug ===')
+		dmsg('    RPC POST data ==> %s\n' % p)
+
+		try:
+			c.request('POST', '/', json.dumps(p), {
+				'Host': self.host,
+				'Authorization': 'Basic ' + base64.b64encode(self.auth_str)
+			})
+		except Exception as e:
+			die(2,'%s\nUnable to connect to bitcoind' % e)
+
+		r = c.getresponse() # returns HTTPResponse instance
+
+		if r.status == 401:
+			m1 = 'RPC authentication error'
+			m2 = 'Check that rpcuser/rpcpassword in Bitcoin config file are correct'
+			m3 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir'
+			m4 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name)
+			die(1,'\n'.join((m1,m2,m3,m4)))
+		elif r.status != 200:
+			die(1,'RPC error: %s %s\n%s' % (r.status, r.reason, r.read()))
+
+		r2 = r.read()
+
+		dmsg('    RPC REPLY data ==> %s\n' % r2)
+
+		if not r2:
+			die(2,'Error: empty reply')
+
+		r3 = json.loads(r2.decode('utf8'), parse_float=decimal.Decimal)
+		ret = []
+
+		for resp in r3 if cf['batch'] else [r3]:
+			if 'error' in resp and resp['error'] != None:
+				die(1,'Bitcoind returned an error: %s' % resp['error'])
+			elif 'result' not in resp:
+				die(1, 'Missing JSON-RPC result\n' + repr(resps))
+			else:
+				ret.append(resp['result'])
+
+		return ret if cf['batch'] else ret[0]
+
+
+	rpcmethods = (
+		'getinfo',
+		'getbalance',
+		'getaddressesbyaccount',
+		'listunspent',
+		'listaccounts',
+		'importaddress',
+		'decoderawtransaction',
+		'createrawtransaction',
+		'signrawtransaction',
+		'sendrawtransaction',
+		'walletpassphrase',
+		'walletlock',
+	)
+
+	for name in rpcmethods:
+		exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name)

+ 0 - 53
mmgen/rpc/__init__.py

@@ -1,53 +0,0 @@
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""
-bitcoin-python - Easy-to-use Bitcoin API client
-"""
-
-
-def connect_to_local(filename=None):
-	"""
-    Connect to default bitcoin instance owned by this user, on this machine.
-
-    Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object.
-
-    Arguments:
-
-        - `filename`: Path to a configuration file in a non-standard location (optional)
-	"""
-	from mmgen.rpc.connection import BitcoinConnection
-	from mmgen.rpc.config import read_default_config
-
-	cfg = read_default_config(filename)
-	port = int(cfg.get('rpcport', '18332' if cfg.get('testnet') else '8332'))
-	rcpuser = cfg.get('rpcuser', '')
-
-	return BitcoinConnection(rcpuser, cfg['rpcpassword'], 'localhost', port)
-
-
-def connect_to_remote(user,password,host='localhost',port=8332,use_https=False):
-	"""
-    Connect to remote or alternative local bitcoin client instance.
-
-    Returns a :class:`~mmgen.rpc.connection.BitcoinConnection` object.
-	"""
-	from mmgen.rpc.connection import BitcoinConnection
-
-	return BitcoinConnection(user, password, host, port, use_https)

+ 0 - 75
mmgen/rpc/config.py

@@ -1,75 +0,0 @@
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""
-Utilities for reading bitcoin configuration files.
-"""
-
-
-def read_config_file(filename):
-	"""
-    Read a simple ``'='``-delimited config file.
-    Raises :const:`IOError` if unable to open file, or :const:`ValueError`
-    if an parse error occurs.
-	"""
-	f = open(filename)
-	try:
-		cfg = {}
-		for line in f:
-			line = line.strip()
-			if line and not line.startswith("#"):
-				try:
-					(key, value) = line.split('=', 1)
-					cfg[key] = value
-				except ValueError:
-					pass  # Happens when line has no '=', ignore
-	finally:
-		f.close()
-	return cfg
-
-
-def read_default_config(filename=None):
-	"""
-    Read bitcoin default configuration from the current user's home directory.
-
-    Arguments:
-
-    - `filename`: Path to a configuration file in a non-standard location (optional)
-	"""
-	if filename is None:
-		import os
-		import platform
-		home = os.getenv("HOME")
-		if not home:
-			raise IOError("Home directory not defined, don't know where to look for config file")
-
-		if platform.system() == "Darwin":
-			location = 'Library/Application Support/Bitcoin/bitcoin.conf'
-		else:
-			location = '.bitcoin/bitcoin.conf'
-		filename = os.path.join(home, location)
-
-	elif filename.startswith("~"):
-		import os
-		filename = os.path.expanduser(filename)
-
-	try:
-		return read_config_file(filename)
-	except (IOError, ValueError):
-		pass  # Cannot read config file, ignore

+ 0 - 766
mmgen/rpc/connection.py

@@ -1,766 +0,0 @@
-# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
-# Added methods for sendrawtransaction(), importaddress()
-#
-# Previous copyright from bitcoin-python/connection.py:
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""
-Connect to Bitcoin server via JSON-RPC.
-"""
-from mmgen.rpc.proxy import JSONRPCException, AuthServiceProxy
-from mmgen.rpc.exceptions import _wrap_exception, WalletPassphraseIncorrect, WalletAlreadyUnlocked
-from mmgen.rpc.data import (ServerInfo, AccountInfo, AddressInfo, TransactionInfo,
-							AddressValidation, WorkItem, MiningInfo)
-
-
-class BitcoinConnection(object):
-	"""
-    A BitcoinConnection object defines a connection to a bitcoin server.
-    It is a thin wrapper around a JSON-RPC API connection.
-
-    Up-to-date for SVN revision 198.
-
-    Arguments to constructor:
-
-    - *user* -- Authenticate as user.
-    - *password* -- Authentication password.
-    - *host* -- Bitcoin JSON-RPC host.
-    - *port* -- Bitcoin JSON-RPC port.
-	"""
-	def __init__(self, user, password, host='localhost', port=8332, use_https=False):
-		"""
-        Create a new bitcoin server connection.
-		"""
-		url = 'http{s}://{user}:{password}@{host}:{port}/'.format(
-			s='s' if use_https else '',
-			user=user, password=password, host=host, port=port)
-		self.url = url
-		try:
-			self.proxy = AuthServiceProxy(url)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-# importaddress <address> [label] [rescan=true]
-	def importaddress(self,address,label=None,rescan=True):
-		try:
-#			return self.proxy.badmethod(address,label) # DEBUG
-			return self.proxy.importaddress(address,label,rescan)
-		except JSONRPCException as e:
-			if e.error['message'] == "Method not found":
-				from mmgen.util import msg
-				msg("""
-*******************************************************************************
-*******************************************************************************
-ERROR: 'importaddress' not found.  Does your bitcoind support watch-only addrs?
-*******************************************************************************
-*******************************************************************************
-""")
-			raise _wrap_exception(e.error)
-
-# sendrawtransaction <hex string> [allowhighfees=false]
-	def sendrawtransaction(self,tx):
-		try:
-			return self.proxy.sendrawtransaction(tx)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def stop(self):
-		"""
-        Stop bitcoin server.
-		"""
-		try:
-			self.proxy.stop()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getblock(self, hash):
-		"""
-        Returns information about the given block hash.
-		"""
-		try:
-			return self.proxy.getblock(hash)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getblockcount(self):
-		"""
-        Returns the number of blocks in the longest block chain.
-		"""
-		try:
-			return self.proxy.getblockcount()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getblockhash(self, index):
-		"""
-        Returns hash of block in best-block-chain at index.
-
-        :param index: index ob the block
-
-		"""
-		try:
-			return self.proxy.getblockhash(index)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getblocknumber(self):
-		"""
-        Returns the block number of the latest block in the longest block chain.
-        Deprecated. Use getblockcount instead.
-		"""
-		return self.getblockcount()
-
-	def getconnectioncount(self):
-		"""
-        Returns the number of connections to other nodes.
-		"""
-		try:
-			return self.proxy.getconnectioncount()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getdifficulty(self):
-		"""
-        Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
-		"""
-		try:
-			return self.proxy.getdifficulty()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getgenerate(self):
-		"""
-        Returns :const:`True` or :const:`False`, depending on whether
-        generation is enabled.
-		"""
-		try:
-			return self.proxy.getgenerate()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def setgenerate(self, generate, genproclimit=None):
-		"""
-        Enable or disable generation (mining) of coins.
-
-        Arguments:
-
-        - *generate* -- is :const:`True` or :const:`False` to turn generation
-        on or off.
-        - *genproclimit* -- Number of processors that are used for generation,
-        -1 is unlimited.
-
-		"""
-		try:
-			if genproclimit is None:
-				return self.proxy.setgenerate(generate)
-			else:
-				return self.proxy.setgenerate(generate, genproclimit)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def gethashespersec(self):
-		"""
-        Returns a recent hashes per second performance measurement while generating.
-		"""
-		try:
-			return self.proxy.gethashespersec()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getinfo(self):
-		"""
-        Returns an :class:`~mmgen.rpc.data.ServerInfo` object containing
-        various state info.
-		"""
-		try:
-			return ServerInfo(**self.proxy.getinfo())
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getmininginfo(self):
-		"""
-        Returns an :class:`~mmgen.rpc.data.MiningInfo` object containing various
-        mining state info.
-		"""
-		try:
-			return MiningInfo(**self.proxy.getmininginfo())
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getnewaddress(self, account=None):
-		"""
-        Returns a new bitcoin address for receiving payments.
-
-        Arguments:
-
-        - *account* -- If account is specified (recommended), it is added to the
-        address book so that payments received with the address will be
-        credited to it.
-
-		"""
-		try:
-			if account is None:
-				return self.proxy.getnewaddress()
-			else:
-				return self.proxy.getnewaddress(account)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getaccountaddress(self, account):
-		"""
-        Returns the current bitcoin address for receiving payments to an account.
-
-        Arguments:
-
-        - *account* -- Account for which the address should be returned.
-
-		"""
-		try:
-			return self.proxy.getaccountaddress(account)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def setaccount(self, bitcoinaddress, account):
-		"""
-        Sets the account associated with the given address.
-
-        Arguments:
-
-        - *bitcoinaddress* -- Bitcoin address to associate.
-        - *account* -- Account to associate the address to.
-
-		"""
-		try:
-			return self.proxy.setaccount(bitcoinaddress, account)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getaccount(self, bitcoinaddress):
-		"""
-        Returns the account associated with the given address.
-
-        Arguments:
-
-        - *bitcoinaddress* -- Bitcoin address to get account for.
-		"""
-		try:
-			return self.proxy.getaccount(bitcoinaddress)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getaddressesbyaccount(self, account):
-		"""
-        Returns the list of addresses for the given account.
-
-        Arguments:
-
-        - *account* -- Account to get list of addresses for.
-		"""
-		try:
-			return self.proxy.getaddressesbyaccount(account)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def sendtoaddress(self, bitcoinaddress, amount, comment=None, comment_to=None):
-		"""
-        Sends *amount* from the server's available balance to *bitcoinaddress*.
-
-        Arguments:
-
-        - *bitcoinaddress* -- Bitcoin address to send to.
-        - *amount* -- Amount to send (float, rounded to the nearest 0.01).
-        - *minconf* -- Minimum number of confirmations required for transferred
-        balance.
-        - *comment* -- Comment for transaction.
-        - *comment_to* -- Comment for to-address.
-
-		"""
-		try:
-			if comment is None:
-				return self.proxy.sendtoaddress(bitcoinaddress, amount)
-			elif comment_to is None:
-				return self.proxy.sendtoaddress(bitcoinaddress, amount, comment)
-			else:
-				return self.proxy.sendtoaddress(bitcoinaddress, amount, comment, comment_to)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getreceivedbyaddress(self, bitcoinaddress, minconf=1):
-		"""
-        Returns the total amount received by a bitcoin address in transactions
-        with at least a certain number of confirmations.
-
-        Arguments:
-
-        - *bitcoinaddress* -- Address to query for total amount.
-
-        - *minconf* -- Number of confirmations to require, defaults to 1.
-		"""
-		try:
-			return self.proxy.getreceivedbyaddress(bitcoinaddress, minconf)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getreceivedbyaccount(self, account, minconf=1):
-		"""
-        Returns the total amount received by addresses with an account in
-        transactions with at least a certain number of confirmations.
-
-        Arguments:
-
-        - *account* -- Account to query for total amount.
-        - *minconf* -- Number of confirmations to require, defaults to 1.
-
-		"""
-		try:
-			return self.proxy.getreceivedbyaccount(account, minconf)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def gettransaction(self, txid):
-		"""
-        Get detailed information about transaction
-
-        Arguments:
-
-        - *txid* -- Transactiond id for which the info should be returned
-
-		"""
-		try:
-			return TransactionInfo(**self.proxy.gettransaction(txid))
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getrawtransaction(self, txid, verbose=True):
-		"""
-        Get transaction raw info
-
-        Arguments:
-
-        - *txid* -- Transactiond id for which the info should be returned.
-        - *verbose* -- If False, return only the "hex" of the transaction.
-
-		"""
-		try:
-			if verbose:
-				return TransactionInfo(**self.proxy.getrawtransaction(txid, 1))
-			return self.proxy.getrawtransaction(txid, 0)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def createrawtransaction(self, inputs, outputs):
-		"""
-        Creates a raw transaction spending given inputs
-        (a list of dictionaries, each containing a transaction id and an output
-        number), sending to given address(es).
-
-        Returns hex-encoded raw transaction.
-
-        Example usage:
-        >>> conn.createrawtransaction(
-               [{"txid": "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c",
-               "vout": 0}],
-               {"mkZBYBiq6DNoQEKakpMJegyDbw2YiNQnHT":50})
-
-
-        Arguments:
-
-        - *inputs* -- A list of {"txid": txid, "vout": n} dictionaries.
-        - *outputs* -- A dictionary mapping (public) addresses to the amount
-                    they are to be paid.
-		"""
-		try:
-			return self.proxy.createrawtransaction(inputs, outputs)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def signrawtransaction(self, hexstring, previous_transactions=None, private_keys=None):
-		"""
-        Sign inputs for raw transaction (serialized, hex-encoded).
-
-        Returns a dictionary with the keys:
-            "hex": raw transaction with signature(s) (hex-encoded string)
-            "complete": 1 if transaction has a complete set of signature(s), 0 if not
-
-        Arguments:
-
-        - *hexstring* -- A hex string of the transaction to sign.
-        - *previous_transactions* -- A (possibly empty) list of dictionaries of
-        the form:
-            {"txid": txid, "vout": n, "scriptPubKey": hex, "redeemScript": hex},
-            representing previous transaction outputs that this transaction depends
-            on but may not yet be in the block chain.
-        - *private_keys* -- A (possibly empty) list of base58-encoded private
-        keys that, if given, will be the only keys used to sign the transaction.
-		"""
-		try:
-			return dict(self.proxy.signrawtransaction(hexstring,
-						previous_transactions, private_keys))
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def decoderawtransaction(self, hexstring):
-		"""
-        Produces a human-readable JSON object for a raw transaction.
-
-        Arguments:
-
-        - *hexstring* -- A hex string of the transaction to be decoded.
-		"""
-		try:
-			return dict(self.proxy.decoderawtransaction(hexstring))
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def listsinceblock(self, block_hash):
-		try:
-			res = self.proxy.listsinceblock(block_hash)
-			res['transactions'] = [TransactionInfo(**x) for x in res['transactions']]
-			return res
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def listreceivedbyaddress(self, minconf=1, includeempty=False):
-		"""
-        Returns a list of addresses.
-
-        Each address is represented with a
-        :class:`~mmgen.rpc.data.AddressInfo` object.
-
-        Arguments:
-
-        - *minconf* -- Minimum number of confirmations before payments are included.
-        - *includeempty* -- Whether to include addresses that haven't received
-        any payments.
-
-		"""
-		try:
-			return [AddressInfo(**x) for x in
-					self.proxy.listreceivedbyaddress(minconf, includeempty)]
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def listaccounts(self, minconf=1, includeWatchonly=False, as_dict=False):
-		"""
-        Returns a list of account names.
-
-        Arguments:
-
-        - *minconf* -- Minimum number of confirmations before payments are included.
-        - *as_dict* -- Returns a dictionary of account names, with their balance as values.
-		"""
-		try:
-			if as_dict:
-				return dict(self.proxy.listaccounts(minconf,includeWatchonly))
-			else:
-				return self.proxy.listaccounts(minconf,includeWatchonly).keys()
-		except JSONRPCException as e:
-			from mmgen.util import msg
-			msg("""
-*******************************************************************************
-*******************************************************************************
-ERROR: 'listaccounts' failed.  Does your bitcoind support watch-only addresses?
-*******************************************************************************
-*******************************************************************************
-""")
-			raise _wrap_exception(e.error)
-
-	def listreceivedbyaccount(self, minconf=1, includeempty=False):
-		"""
-        Returns a list of accounts.
-
-        Each account is represented with a :class:`~mmgen.rpc.data.AccountInfo` object.
-
-        Arguments:
-
-        - *minconf* -- Minimum number of confirmations before payments are included.
-
-        - *includeempty* -- Whether to include addresses that haven't received any payments.
-		"""
-		try:
-			return [AccountInfo(**x) for x in
-					self.proxy.listreceivedbyaccount(minconf, includeempty)]
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def listtransactions(self, account=None, count=10, from_=0, address=None):
-		"""
-        Returns a list of the last transactions for an account.
-
-        Each transaction is represented with a :class:`~mmgen.rpc.data.TransactionInfo` object.
-
-        Arguments:
-
-        - *account* -- Account to list transactions from. Return transactions from
-                    all accounts if None.
-        - *count* -- Number of transactions to return.
-        - *from_* -- Skip the first <from_> transactions.
-        - *address* -- Receive address to consider
-		"""
-		accounts = [account] if account is not None else self.listaccounts(as_dict=True).iterkeys()
-		try:
-			return [TransactionInfo(**tx) for acc in accounts for
-					tx in self.proxy.listtransactions(acc, count, from_) if
-					address is None or tx["address"] == address]
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def backupwallet(self, destination):
-		"""
-        Safely copies ``wallet.dat`` to *destination*, which can be a directory or a path
-        with filename.
-
-        Arguments:
-        - *destination* -- directory or path with filename to backup wallet to.
-
-		"""
-		try:
-			return self.proxy.backupwallet(destination)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def validateaddress(self, validateaddress):
-		"""
-        Validate a bitcoin address and return information for it.
-
-        The information is represented by a :class:`~mmgen.rpc.data.AddressValidation` object.
-
-        Arguments: -- Address to validate.
-
-
-        - *validateaddress*
-		"""
-		try:
-			return AddressValidation(**self.proxy.validateaddress(validateaddress))
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getbalance(self, account=None, minconf=None):
-		"""
-        Get the current balance, either for an account or the total server balance.
-
-        Arguments:
-        - *account* -- If this parameter is specified, returns the balance in the account.
-        - *minconf* -- Minimum number of confirmations required for transferred balance.
-
-		"""
-		args = []
-		if account:
-			args.append(account)
-			if minconf is not None:
-				args.append(minconf)
-		try:
-			return self.proxy.getbalance(*args)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def move(self, fromaccount, toaccount, amount, minconf=1, comment=None):
-		"""
-        Move from one account in your wallet to another.
-
-        Arguments:
-
-        - *fromaccount* -- Source account name.
-        - *toaccount* -- Destination account name.
-        - *amount* -- Amount to transfer.
-        - *minconf* -- Minimum number of confirmations required for transferred balance.
-        - *comment* -- Comment to add to transaction log.
-
-		"""
-		try:
-			if comment is None:
-				return self.proxy.move(fromaccount, toaccount, amount, minconf)
-			else:
-				return self.proxy.move(fromaccount, toaccount, amount, minconf, comment)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def sendfrom(self, fromaccount, tobitcoinaddress, amount, minconf=1, comment=None,
-				comment_to=None):
-		"""
-        Sends amount from account's balance to bitcoinaddress. This method will fail
-        if there is less than amount bitcoins with minconf confirmations in the account's
-        balance (unless account is the empty-string-named default account; it
-        behaves like the sendtoaddress method). Returns transaction ID on success.
-
-        Arguments:
-
-        - *fromaccount* -- Account to send from.
-        - *tobitcoinaddress* -- Bitcoin address to send to.
-        - *amount* -- Amount to send (float, rounded to the nearest 0.01).
-        - *minconf* -- Minimum number of confirmations required for transferred balance.
-        - *comment* -- Comment for transaction.
-        - *comment_to* -- Comment for to-address.
-
-		"""
-		try:
-			if comment is None:
-				return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf)
-			elif comment_to is None:
-				return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment)
-			else:
-				return self.proxy.sendfrom(fromaccount, tobitcoinaddress, amount, minconf, comment, comment_to)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def sendmany(self, fromaccount, todict, minconf=1, comment=None):
-		"""
-        Sends specified amounts from account's balance to bitcoinaddresses.
-        This method will fail if there is less than total amount bitcoins with
-        minconf confirmations in the account's balance (unless account is the
-        empty-string-named default account; Returns transaction ID on
-        success.
-
-        Arguments:
-
-        - *fromaccount* -- Account to send from.
-        - *todict* -- Dictionary with Bitcoin addresses as keys and amounts as
-        values.
-        - *minconf* -- Minimum number of confirmations required for transferred
-        balance.
-        - *comment* -- Comment for transaction.
-
-		"""
-		try:
-			if comment is None:
-				return self.proxy.sendmany(fromaccount, todict, minconf)
-			else:
-				return self.proxy.sendmany(fromaccount, todict, minconf, comment)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def verifymessage(self, bitcoinaddress, signature, message):
-		"""
-        Verifies a signature given the bitcoinaddress used to sign,
-        the signature itself, and the message that was signed.
-        Returns :const:`True` if the signature is valid, and :const:`False` if it is invalid.
-
-        Arguments:
-
-        - *bitcoinaddress* -- the bitcoinaddress used to sign the message
-        - *signature* -- the signature to be verified
-        - *message* -- the message that was originally signed
-
-		"""
-		try:
-			return self.proxy.verifymessage(bitcoinaddress, signature, message)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def getwork(self, data=None):
-		"""
-        Get work for remote mining, or submit result.
-        If data is specified, the server tries to solve the block
-        using the provided data and returns :const:`True` if it was successful.
-        If not, the function returns formatted hash data (:class:`~mmgen.rpc.data.WorkItem`)
-        to work on.
-
-        Arguments:
-
-        - *data* -- Result from remote mining.
-
-		"""
-		try:
-			if data is None:
-				# Only if no data provided, it returns a WorkItem
-				return WorkItem(**self.proxy.getwork())
-			else:
-				return self.proxy.getwork(data)
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def listunspent(self, minconf=1, maxconf=999999):
-		"""
-        Returns a list of unspent transaction inputs in the wallet.
-
-        Arguments:
-
-        - *minconf* -- Minimum number of confirmations required to be listed.
-
-        - *maxconf* -- Maximal number of confirmations allowed to be listed.
-
-
-		"""
-		try:
-			return [TransactionInfo(**tx) for tx in
-					self.proxy.listunspent(minconf, maxconf)]
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def keypoolrefill(self):
-		"Fills the keypool, requires wallet passphrase to be set."
-		try:
-			self.proxy.keypoolrefill()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def walletpassphrase(self, passphrase, timeout, dont_raise=False):
-		"""
-        Stores the wallet decryption key in memory for <timeout> seconds.
-
-        - *passphrase* -- The wallet passphrase.
-
-        - *timeout* -- Time in seconds to keep the wallet unlocked
-                    (by keeping the passphrase in memory).
-
-        - *dont_raise* -- instead of raising `~mmgen.rpc.exceptions.WalletPassphraseIncorrect`
-                       return False.
-		"""
-		try:
-			self.proxy.walletpassphrase(passphrase, timeout)
-			return True
-		except JSONRPCException as e:
-			json_exception = _wrap_exception(e.error)
-			if dont_raise:
-				if isinstance(json_exception, WalletPassphraseIncorrect):
-					return False
-				elif isinstance(json_exception, WalletAlreadyUnlocked):
-					return True
-			raise json_exception
-
-	def walletlock(self):
-		"""
-        Removes the wallet encryption key from memory, locking the wallet.
-        After calling this method, you will need to call walletpassphrase
-        again before being able to call any methods which require the wallet
-        to be unlocked.
-		"""
-		try:
-			return self.proxy.walletlock()
-		except JSONRPCException as e:
-			raise _wrap_exception(e.error)
-
-	def walletpassphrasechange(self, oldpassphrase, newpassphrase, dont_raise=False):
-		"""
-        Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>.
-
-        Arguments:
-
-        - *dont_raise* -- instead of raising
-               `~mmgen.rpc.exceptions.WalletPassphraseIncorrect` return False.
-		"""
-		try:
-			self.proxy.walletpassphrasechange(oldpassphrase, newpassphrase)
-			return True
-		except JSONRPCException as e:
-			json_exception = _wrap_exception(e.error)
-			if dont_raise and isinstance(json_exception, WalletPassphraseIncorrect):
-				return False
-			raise json_exception

+ 0 - 168
mmgen/rpc/data.py

@@ -1,168 +0,0 @@
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""
-Bitcoin RPC service, data objects.
-"""
-from mmgen.rpc.util import DStruct
-
-
-class ServerInfo(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getinfo`.
-
-    - *errors* -- Number of errors.
-
-    - *blocks* -- Number of blocks.
-
-    - *paytxfee* -- Amount of transaction fee to pay.
-
-    - *keypoololdest* -- Oldest key in keypool.
-
-    - *genproclimit* -- Processor limit for generation.
-
-    - *connections* -- Number of connections to other clients.
-
-    - *difficulty* -- Current generating difficulty.
-
-    - *testnet* -- True if connected to testnet, False if on real network.
-
-    - *version* -- Bitcoin client version.
-
-    - *proxy* -- Proxy configured in client.
-
-    - *hashespersec* -- Number of hashes per second (if generation enabled).
-
-    - *balance* -- Total current server balance.
-
-    - *generate* -- True if generation enabled, False if not.
-
-    - *unlocked_until* -- Timestamp (seconds since epoch) after which the wallet
-                          will be/was locked (if wallet encryption is enabled).
-
-    """
-
-
-class AccountInfo(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaccount`.
-
-    - *account* -- The account of the receiving address.
-
-    - *amount* -- Total amount received by the address.
-
-    - *confirmations* -- Number of confirmations of the most recent transaction included.
-
-    """
-
-
-class AddressInfo(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listreceivedbyaddress`.
-
-    - *address* -- Receiving address.
-
-    - *account* -- The account of the receiving address.
-
-    - *amount* -- Total amount received by the address.
-
-    - *confirmations* -- Number of confirmations of the most recent transaction included.
-
-    """
-
-
-class TransactionInfo(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.listtransactions`.
-
-    - *account* -- account name.
-
-    - *address* -- the address bitcoins were sent to, or received from.
-
-    - *category* -- will be generate, send, receive, or move.
-
-    - *amount* -- amount of transaction.
-
-    - *fee* -- Fee (if any) paid (only for send transactions).
-
-    - *confirmations* -- number of confirmations (only for generate/send/receive).
-
-    - *txid* -- transaction ID (only for generate/send/receive).
-
-    - *otheraccount* -- account funds were moved to or from (only for move).
-
-    - *message* -- message associated with transaction (only for send).
-
-    - *to* -- message-to associated with transaction (only for send).
-    """
-
-
-class AddressValidation(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.validateaddress`.
-
-    - *isvalid* -- Validatity of address (:const:`True` or :const:`False`).
-
-    - *ismine* -- :const:`True` if the address is in the server's wallet.
-
-    - *address* -- Bitcoin address.
-
-    """
-
-
-class WorkItem(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getwork`.
-
-    - *midstate* -- Precomputed hash state after hashing the first half of the data.
-
-    - *data* -- Block data.
-
-    - *hash1* -- Formatted hash buffer for second hash.
-
-    - *target* -- Little endian hash target.
-
-    """
-
-
-class MiningInfo(DStruct):
-    """
-    Information object returned by :func:`~mmgen.rpc.connection.BitcoinConnection.getmininginfo`.
-
-    - *blocks* -- Number of blocks.
-
-    - *currentblocksize* -- Size of current block.
-
-    - *currentblocktx* -- Number of transactions in current block.
-
-    - *difficulty* -- Current generating difficulty.
-
-    - *errors* -- Number of errors.
-
-    - *generate* -- True if generation enabled, False if not.
-
-    - *genproclimit* -- Processor limit for generation.
-
-    - *hashespersec* -- Number of hashes per second (if generation enabled).
-
-    - *pooledtx* -- Number of pooled transactions.
-
-    - *testnet* -- True if connected to testnet, False if on real network.
-
-    """

+ 0 - 203
mmgen/rpc/exceptions.py

@@ -1,203 +0,0 @@
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""
-Exception definitions.
-"""
-
-
-class BitcoinException(Exception):
-	"""
-    Base class for exceptions received from Bitcoin server.
-
-    - *code* -- Error code from ``bitcoind``.
-	"""
-	# Standard JSON-RPC 2.0 errors
-	INVALID_REQUEST  = -32600,
-	METHOD_NOT_FOUND = -32601,
-	INVALID_PARAMS   = -32602,
-	INTERNAL_ERROR   = -32603,
-	PARSE_ERROR      = -32700,
-
-	# General application defined errors
-	MISC_ERROR                  = -1  # std::exception thrown in command handling
-	FORBIDDEN_BY_SAFE_MODE      = -2  # Server is in safe mode, and command is not allowed in safe mode
-	TYPE_ERROR                  = -3  # Unexpected type was passed as parameter
-	INVALID_ADDRESS_OR_KEY      = -5  # Invalid address or key
-	OUT_OF_MEMORY               = -7  # Ran out of memory during operation
-	INVALID_PARAMETER           = -8  # Invalid, missing or duplicate parameter
-	DATABASE_ERROR              = -20 # Database error
-	DESERIALIZATION_ERROR       = -22 # Error parsing or validating structure in raw format
-
-	# P2P client errors
-	CLIENT_NOT_CONNECTED        = -9  # Bitcoin is not connected
-	CLIENT_IN_INITIAL_DOWNLOAD  = -10 # Still downloading initial blocks
-
-	# Wallet errors
-	WALLET_ERROR                = -4  # Unspecified problem with wallet (key not found etc.)
-	WALLET_INSUFFICIENT_FUNDS   = -6  # Not enough funds in wallet or account
-	WALLET_INVALID_ACCOUNT_NAME = -11 # Invalid account name
-	WALLET_KEYPOOL_RAN_OUT      = -12 # Keypool ran out, call keypoolrefill first
-	WALLET_UNLOCK_NEEDED        = -13 # Enter the wallet passphrase with walletpassphrase first
-	WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect
-	WALLET_WRONG_ENC_STATE      = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
-	WALLET_ENCRYPTION_FAILED    = -16 # Failed to encrypt the wallet
-	WALLET_ALREADY_UNLOCKED     = -17 # Wallet is already unlocked
-
-	def __init__(self, error):
-		Exception.__init__(self, error['message'])
-		self.code = error['code']
-
-
-##### General application defined errors
-class SafeMode(BitcoinException):
-	"""
-    Operation denied in safe mode (run ``bitcoind`` with ``-disablesafemode``).
-	"""
-
-
-class JSONTypeError(BitcoinException):
-	"""
-    Unexpected type was passed as parameter
-	"""
-InvalidAmount = JSONTypeError  # Backwards compatibility
-
-
-class InvalidAddressOrKey(BitcoinException):
-	"""
-    Invalid address or key.
-	"""
-InvalidTransactionID = InvalidAddressOrKey  # Backwards compatibility
-
-
-class OutOfMemory(BitcoinException):
-	"""
-    Out of memory during operation.
-	"""
-
-
-class InvalidParameter(BitcoinException):
-	"""
-    Invalid parameter provided to RPC call.
-	"""
-
-
-##### Client errors
-class ClientException(BitcoinException):
-	"""
-    P2P network error.
-    This exception is never raised but functions as a superclass
-    for other P2P client exceptions.
-	"""
-
-
-class NotConnected(ClientException):
-	"""
-    Not connected to any peers.
-	"""
-
-
-class DownloadingBlocks(ClientException):
-	"""
-    Client is still downloading blocks.
-	"""
-
-
-##### Wallet errors
-class WalletError(BitcoinException):
-	"""
-    Unspecified problem with wallet (key not found etc.)
-	"""
-SendError = WalletError  # Backwards compatibility
-
-class InsufficientFunds(WalletError):
-	"""
-    Insufficient funds to complete transaction in wallet or account
-	"""
-
-class InvalidAccountName(WalletError):
-	"""
-    Invalid account name
-	"""
-
-
-class KeypoolRanOut(WalletError):
-	"""
-    Keypool ran out, call keypoolrefill first
-	"""
-
-
-class WalletUnlockNeeded(WalletError):
-	"""
-    Enter the wallet passphrase with walletpassphrase first
-	"""
-
-
-class WalletPassphraseIncorrect(WalletError):
-	"""
-    The wallet passphrase entered was incorrect
-	"""
-
-
-class WalletWrongEncState(WalletError):
-	"""
-    Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
-	"""
-
-
-class WalletEncryptionFailed(WalletError):
-	"""
-    Failed to encrypt the wallet
-	"""
-
-
-class WalletAlreadyUnlocked(WalletError):
-	"""
-    Wallet is already unlocked
-	"""
-
-
-# For convenience, we define more specific exception classes
-# for the more common errors.
-_exception_map = {
-	BitcoinException.FORBIDDEN_BY_SAFE_MODE: SafeMode,
-	BitcoinException.TYPE_ERROR: JSONTypeError,
-	BitcoinException.WALLET_ERROR: WalletError,
-	BitcoinException.INVALID_ADDRESS_OR_KEY: InvalidAddressOrKey,
-	BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
-	BitcoinException.OUT_OF_MEMORY: OutOfMemory,
-	BitcoinException.INVALID_PARAMETER: InvalidParameter,
-	BitcoinException.CLIENT_NOT_CONNECTED: NotConnected,
-	BitcoinException.CLIENT_IN_INITIAL_DOWNLOAD: DownloadingBlocks,
-	BitcoinException.WALLET_INSUFFICIENT_FUNDS: InsufficientFunds,
-	BitcoinException.WALLET_INVALID_ACCOUNT_NAME: InvalidAccountName,
-	BitcoinException.WALLET_KEYPOOL_RAN_OUT: KeypoolRanOut,
-	BitcoinException.WALLET_UNLOCK_NEEDED: WalletUnlockNeeded,
-	BitcoinException.WALLET_PASSPHRASE_INCORRECT: WalletPassphraseIncorrect,
-	BitcoinException.WALLET_WRONG_ENC_STATE: WalletWrongEncState,
-	BitcoinException.WALLET_ENCRYPTION_FAILED: WalletEncryptionFailed,
-	BitcoinException.WALLET_ALREADY_UNLOCKED: WalletAlreadyUnlocked,
-}
-
-
-def _wrap_exception(error):
-	"""
-    Convert a JSON error object to a more specific Bitcoin exception.
-	"""
-	return _exception_map.get(error['code'], BitcoinException)(error)

+ 0 - 142
mmgen/rpc/proxy.py

@@ -1,142 +0,0 @@
-"""
-  Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
-  Added http_timeout from mmgen.globalvars
-
-  Previous copyright from bitcoin-python/proxy.py:
-
-  Copyright 2011 Jeff Garzik
-
-  AuthServiceProxy has the following improvements over python-jsonrpc's
-  ServiceProxy class:
-
-  - HTTP connections persist for the life of the AuthServiceProxy object
-    (if server supports HTTP/1.1)
-  - sends protocol 'version', per JSON-RPC 1.1
-  - sends proper, incrementing 'id'
-  - sends Basic HTTP authentication headers
-  - parses all JSON numbers that look like floats as Decimal
-  - uses standard Python json lib
-
-  Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
-
-  Copyright (c) 2007 Jan-Klaas Kollhof
-
-  This file is part of jsonrpc.
-
-  jsonrpc is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  This software is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with this software; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-"""
-
-try:
-	import http.client as httplib
-except ImportError:
-	import httplib
-import base64
-import json
-import decimal
-try:
-	import urllib.parse as urlparse
-except ImportError:
-	import urlparse
-
-USER_AGENT = "AuthServiceProxy/0.1"
-
-class JSONRPCException(Exception):
-	def __init__(self, rpcError):
-		Exception.__init__(self)
-		self.error = rpcError
-
-
-import mmgen.globalvars as g
-
-class AuthServiceProxy(object):
-	def __init__(self, serviceURL, serviceName = None):
-
-		self.__serviceURL = serviceURL
-		self.__serviceName = serviceName
-		self.__url = urlparse.urlparse(serviceURL)
-		if self.__url.port is None:
-			port = 80
-		else:
-			port = self.__url.port
-		self.__idcnt = 0
-		authpair = "%s:%s" % (self.__url.username, self.__url.password)
-		authpair = authpair.encode('utf8')
-		self.__authhdr = "Basic ".encode('utf8') + base64.b64encode(authpair)
-
-		http_timeout = g.http_timeout
-
-		if self.__url.scheme == 'https':
-			self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
-					None, None, False, timeout=http_timeout)
-		else:
-			self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
-					False, timeout=http_timeout)
-
-	def __getattr__(self, name):
-		if self.__serviceName != None:
-			name = "%s.%s" % (self.__serviceName, name)
-		return AuthServiceProxy(self.__serviceURL, name)
-
-	def __call__(self, *args):
-		self.__idcnt += 1
-
-		postdata = json.dumps({
-				'version': '1.1',
-				'method': self.__serviceName,
-				'params': args,
-				'id': self.__idcnt})
-		try:
-			self.__conn.request('POST', self.__url.path, postdata,
-					{ 'Host' : self.__url.hostname,
-					'User-Agent' : USER_AGENT,
-					'Authorization' : self.__authhdr,
-					'Content-type' : 'application/json' })
-		except:
-			from mmgen.util import die,red
-			die(1,red("Unable to connect to bitcoind"))
-
-		httpresp = self.__conn.getresponse()
-		if httpresp is None:
-			raise JSONRPCException({
-					'code' : -342, 'message' : 'missing HTTP response from server'})
-
-		resp = httpresp.read()
-		resp = resp.decode('utf8')
-		resp = json.loads(resp, parse_float=decimal.Decimal)
-		if 'error' in resp and resp['error'] != None:
-			raise JSONRPCException(resp['error'])
-		elif 'result' not in resp:
-			raise JSONRPCException({
-					'code' : -343, 'message' : 'missing JSON-RPC result'})
-		else:
-			return resp['result']
-
-	def _batch(self, rpc_call_list):
-		postdata = json.dumps(list(rpc_call_list))
-		self.__conn.request('POST', self.__url.path, postdata,
-				{ 'Host' : self.__url.hostname,
-				'User-Agent' : USER_AGENT,
-				'Authorization' : self.__authhdr,
-				'Content-type' : 'application/json' })
-
-		httpresp = self.__conn.getresponse()
-		if httpresp is None:
-			raise JSONRPCException({
-					'code' : -342, 'message' : 'missing HTTP response from server'})
-
-		resp = httpresp.read()
-		resp = resp.decode('utf8')
-		resp = json.loads(resp, parse_float=decimal.Decimal)
-		return resp

+ 0 - 49
mmgen/rpc/util.py

@@ -1,49 +0,0 @@
-# Copyright (c) 2010 Witchspace <witchspace81@gmail.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-"""Generic utilities used by bitcoin client library."""
-from copy import copy
-
-
-class DStruct(object):
-	"""
-    Simple dynamic structure, like :const:`collections.namedtuple` but more flexible
-    (and less memory-efficient)
-	"""
-	# Default arguments. Defaults are *shallow copied*, to allow defaults such as [].
-	_fields = []
-	_defaults = {}
-
-	def __init__(self, *args_t, **args_d):
-		# order
-		if len(args_t) > len(self._fields):
-			raise TypeError("Number of arguments is larger than of predefined fields")
-		# Copy default values
-		for (k, v) in self._defaults.iteritems():
-			self.__dict__[k] = copy(v)
-		# Set pass by value arguments
-		self.__dict__.update(zip(self._fields, args_t))
-		# dict
-		self.__dict__.update(args_d)
-
-	def __repr__(self):
-		return '{module}.{classname}({slots})'.format(
-			module=self.__class__.__module__, classname=self.__class__.__name__,
-			slots=", ".join('{k}={v!r}'.format(k=k, v=v) for k, v in
-							self.__dict__.iteritems()))

+ 202 - 236
mmgen/seed.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -19,22 +19,21 @@
 """
 seed.py:  Seed-related classes and methods for the MMGen suite
 """
-import sys,os
+
+import os
 from binascii import hexlify,unhexlify
 
-import mmgen.globalvars as g
-import mmgen.opt as opt
+from mmgen.common import *
 from mmgen.bitcoin import b58encode_pad,b58decode_pad,b58_lens
 from mmgen.obj import *
 from mmgen.filename import *
-from mmgen.util import *
 from mmgen.crypto import *
 
 pnm = g.proj_name
 
 def check_usr_seed_len(seed_len):
 	if opt.seed_len != seed_len and 'seed_len' in opt.set_by_user:
-		m = "ERROR: requested seed length (%s) " + \
+		m = 'ERROR: requested seed length (%s) ' + \
 			"doesn't match seed length of source (%s)"
 		die(1, m % (opt.seed_len,seed_len))
 
@@ -45,7 +44,7 @@ class Seed(MMGenObject):
 			# Truncate random data for smaller seed lengths
 			seed_bin = sha256(get_random(1033)).digest()[:opt.seed_len/8]
 		elif len(seed_bin)*8 not in g.seed_lens:
-			die(3,"%s: invalid seed length" % len(seed_bin))
+			die(3,'%s: invalid seed length' % len(seed_bin))
 
 		self.data      = seed_bin
 		self.hexdata   = hexlify(seed_bin)
@@ -55,8 +54,8 @@ class Seed(MMGenObject):
 
 class SeedSource(MMGenObject):
 
-	desc = g.proj_name + " seed source"
-	file_mode = "text"
+	desc = g.proj_name + ' seed source'
+	file_mode = 'text'
 	stdin_ok = False
 	ask_tty = True
 	no_tty  = False
@@ -71,8 +70,8 @@ class SeedSource(MMGenObject):
 		def die_on_opt_mismatch(opt,sstype):
 			opt_sstype = cls.fmt_code_to_sstype(opt)
 			compare_or_die(
-				opt_sstype.__name__, "input format requested on command line",
-				sstype.__name__,     "input file format"
+				opt_sstype.__name__, 'input format requested on command line',
+				sstype.__name__,     'input file format'
 			)
 
 		if ss:
@@ -83,31 +82,31 @@ class SeedSource(MMGenObject):
 			me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet
 			me.seed = ss.seed
 			me.ss_in = ss
-			me.op = ("conv","pwchg_new")[int(passchg)]
+			me.op = ('conv','pwchg_new')[bool(passchg)]
 		elif fn or opt.hidden_incog_input_params:
 			if fn:
 				f = Filename(fn)
 				sstype = cls.ext_to_sstype(f.ext)
 			else:
-				fn = opt.hidden_incog_input_params.split(",")[0]
-				f  = Filename(fn,ftype="hincog")
-				sstype = cls.fmt_code_to_sstype("hincog")
+				fn = opt.hidden_incog_input_params.split(',')[0]
+				f  = Filename(fn,ftype='hincog')
+				sstype = cls.fmt_code_to_sstype('hincog')
 
 			if opt.in_fmt and not ignore_in_fmt:
 				die_on_opt_mismatch(opt.in_fmt,sstype)
 
 			me = super(cls,cls).__new__(sstype)
 			me.infile = f
-			me.op = ("old","pwchg_old")[int(passchg)]
+			me.op = ('old','pwchg_old')[bool(passchg)]
 		elif opt.in_fmt:  # Input format
 			sstype = cls.fmt_code_to_sstype(opt.in_fmt)
 			me = super(cls,cls).__new__(sstype)
-			me.op = ("old","pwchg_old")[int(passchg)]
+			me.op = ('old','pwchg_old')[bool(passchg)]
 		else: # Called with no inputs - initialize with random seed
 			sstype = cls.fmt_code_to_sstype(opt.out_fmt)
 			me = super(cls,cls).__new__(sstype or Wallet) # default: Wallet
 			me.seed = Seed(seed_bin=seed or None)
-			me.op = "new"
+			me.op = 'new'
 
 		return me
 
@@ -130,38 +129,38 @@ class SeedSource(MMGenObject):
 			self._decrypt_retry()
 		else:
 			if not self.stdin_ok:
-				die(1,"Reading from standard input not supported for %s format"
+				die(1,'Reading from standard input not supported for %s format'
 						% self.desc)
 			self._deformat_retry()
 			self._decrypt_retry()
 
-		m = ("",", seed length %s" % self.seed.length)[int(self.seed.length != 256)]
-		qmsg("Valid %s for Seed ID %s%s" % (self.desc,self.seed.sid,m))
+		m = ('',', seed length %s' % self.seed.length)[self.seed.length!=256]
+		qmsg('Valid %s for Seed ID %s%s' % (self.desc,self.seed.sid,m))
 
 	def _get_data(self):
 		if hasattr(self,'infile'):
 			self.fmt_data = get_data_from_file(self.infile.name,self.desc,
-								binary=self.file_mode=="binary")
+								binary=self.file_mode=='binary')
 		else:
 			self.fmt_data = get_data_from_user(self.desc)
 
 	def _deformat_once(self):
 		self._get_data()
 		if not self._deformat():
-			die(2,"Invalid format for input data")
+			die(2,'Invalid format for input data')
 
 	def _deformat_retry(self):
 		while True:
 			self._get_data()
 			if self._deformat(): break
-			msg("Trying again...")
+			msg('Trying again...')
 
 	def _decrypt_retry(self):
 		while True:
 			if self._decrypt(): break
 			if opt.passwd_file:
-				die(2,"Passphrase from password file, so exiting")
-			msg("Trying again...")
+				die(2,'Passphrase from password file, so exiting')
+			msg('Trying again...')
 
 	subclasses = []
 
@@ -185,7 +184,7 @@ class SeedSource(MMGenObject):
 	def fmt_code_to_sstype(cls,fmt_code):
 		if not fmt_code: return None
 		for c in cls._get_subclasses():
-			if hasattr(c,"fmt_codes") and fmt_code in c.fmt_codes:
+			if hasattr(c,'fmt_codes') and fmt_code in c.fmt_codes:
 				return c
 		return None
 
@@ -193,20 +192,20 @@ class SeedSource(MMGenObject):
 	def ext_to_sstype(cls,ext):
 		if not ext: return None
 		for c in cls._get_subclasses():
-			if hasattr(c,"ext") and ext == c.ext:
+			if hasattr(c,'ext') and ext == c.ext:
 				return c
 		return None
 
 	@classmethod
 	def format_fmt_codes(cls):
-		d = [(c.__name__,",".join(c.fmt_codes)) for c in cls._get_subclasses()
-				if hasattr(c,"fmt_codes")]
+		d = [(c.__name__,','.join(c.fmt_codes)) for c in cls._get_subclasses()
+				if hasattr(c,'fmt_codes')]
 		w = max([len(a) for a,b in d])
-		ret = ["{:<{w}}  {}".format(a,b,w=w) for a,b in [
-			("Format","Valid codes"),
-			("------","-----------")
+		ret = ['{:<{w}}  {}'.format(a,b,w=w) for a,b in [
+			('Format','Valid codes'),
+			('------','-----------')
 			] + sorted(d)]
-		return "\n".join(ret) + "\n"
+		return '\n'.join(ret) + '\n'
 
 	def get_fmt_data(self):
 		self._format()
@@ -218,7 +217,7 @@ class SeedSource(MMGenObject):
 			'desc':     self.desc,
 			'ask_tty':  self.ask_tty,
 			'no_tty':   self.no_tty,
-			'binary':   self.file_mode == "binary"
+			'binary':   self.file_mode == 'binary'
 		}
 		write_data_to_file(self._filename(),self.fmt_data,**kwargs)
 
@@ -240,17 +239,17 @@ an empty passphrase, just hit ENTER twice.
 	""".strip()
 	}
 
-	def _get_hash_preset_from_user(self,hp,desc_suf=""):
+	def _get_hash_preset_from_user(self,hp,desc_suf=''):
 # 					hp=a,
-		n = ("","old ")[int(self.op=="pwchg_old")]
-		m,n = (("to accept the default",n),("to reuse the old","new "))[
-						int(self.op=="pwchg_new")]
+		n = ('','old ')[self.op=='pwchg_old']
+		m,n = (('to accept the default',n),('to reuse the old','new '))[
+						int(self.op=='pwchg_new')]
 		fs = "Enter {}hash preset for {}{}{},\n or hit ENTER {} value ('{}'): "
 		p = fs.format(
 			n,
-			("","new ")[int(self.op=="new")],
+			('','new ')[self.op=='new'],
 			self.desc,
-			(""," "+desc_suf)[int(bool(desc_suf))],
+			('',' '+desc_suf)[bool(desc_suf)],
 			m,
 			hp
 		)
@@ -261,14 +260,14 @@ an empty passphrase, just hit ENTER twice.
 					self.ssdata.hash_preset = ret
 					return ret
 				else:
-					msg("Invalid input.  Valid choices are %s" %
-							", ".join(sorted(g.hash_presets.keys())))
+					msg('Invalid input.  Valid choices are %s' %
+							', '.join(sorted(g.hash_presets.keys())))
 			else:
 				self.ssdata.hash_preset = hp
 				return hp
 
-	def _get_hash_preset(self,desc_suf=""):
-		if hasattr(self,"ss_in") and hasattr(self.ss_in.ssdata,"hash_preset"):
+	def _get_hash_preset(self,desc_suf=''):
+		if hasattr(self,'ss_in') and hasattr(self.ss_in.ssdata,'hash_preset'):
 			old_hp = self.ss_in.ssdata.hash_preset
 			if opt.keep_hash_preset:
 				qmsg("Reusing hash preset '%s' at user request" % old_hp)
@@ -280,9 +279,9 @@ an empty passphrase, just hit ENTER twice.
 			else: # Prompt, using old value as default
 				hp = self._get_hash_preset_from_user(old_hp,desc_suf)
 
-			if (not opt.keep_hash_preset) and self.op == "pwchg_new":
-				m = ("changed to '%s'" % hp,"unchanged")[int(hp==old_hp)]
-				qmsg("Hash preset %s" % m)
+			if (not opt.keep_hash_preset) and self.op == 'pwchg_new':
+				m = ("changed to '%s'" % hp,'unchanged')[hp==old_hp]
+				qmsg('Hash preset %s' % m)
 		elif 'hash_preset' in opt.set_by_user:
 			self.ssdata.hash_preset = opt.hash_preset
 			qmsg("Using hash preset '%s' requested on command line"%opt.hash_preset)
@@ -290,44 +289,43 @@ an empty passphrase, just hit ENTER twice.
 			self._get_hash_preset_from_user(opt.hash_preset,desc_suf)
 
 	def _get_new_passphrase(self):
-		desc = "{}passphrase for {}{}".format(
-				("","new ")[int(self.op=="pwchg_new")],
-				("","new ")[int(self.op in ("new","conv"))],
+		desc = '{}passphrase for {}{}'.format(
+				('','new ')[self.op=='pwchg_new'],
+				('','new ')[self.op in ('new','conv')],
 				self.desc
 			)
 		if opt.passwd_file:
 			w = pwfile_reuse_warning()
-			pw = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w))
+			pw = ' '.join(get_words_from_file(opt.passwd_file,desc,silent=w))
 		elif opt.echo_passphrase:
-			pw = " ".join(get_words_from_user("Enter %s: " % desc))
+			pw = ' '.join(get_words_from_user('Enter %s: ' % desc))
 		else:
 			for i in range(g.passwd_max_tries):
-				pw = " ".join(get_words_from_user("Enter %s: " % desc))
-				pw2 = " ".join(get_words_from_user("Repeat passphrase: "))
-				dmsg("Passphrases: [%s] [%s]" % (pw,pw2))
+				pw = ' '.join(get_words_from_user('Enter %s: ' % desc))
+				pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
+				dmsg('Passphrases: [%s] [%s]' % (pw,pw2))
 				if pw == pw2:
-					vmsg("Passphrases match"); break
-				else: msg("Passphrases do not match.  Try again.")
+					vmsg('Passphrases match'); break
+				else: msg('Passphrases do not match.  Try again.')
 			else:
-				msg("User failed to duplicate passphrase in %s attempts" %
+				die(2,'User failed to duplicate passphrase in %s attempts' %
 						g.passwd_max_tries)
-				sys.exit(2)
 
-		if pw == "": qmsg("WARNING: Empty passphrase")
+		if pw == '': qmsg('WARNING: Empty passphrase')
 		self.ssdata.passwd = pw
 		return pw
 
-	def _get_passphrase(self,desc_suf=""):
-		desc ="{}passphrase for {}{}".format(
-			("","old ")[int(self.op=="pwchg_old")],
+	def _get_passphrase(self,desc_suf=''):
+		desc ='{}passphrase for {}{}'.format(
+			('','old ')[self.op=='pwchg_old'],
 			self.desc,
-			(""," "+desc_suf)[int(bool(desc_suf))]
+			('',' '+desc_suf)[bool(desc_suf)]
 		)
 		if opt.passwd_file:
 			w = pwfile_reuse_warning()
-			ret = " ".join(get_words_from_file(opt.passwd_file,desc,silent=w))
+			ret = ' '.join(get_words_from_file(opt.passwd_file,desc,silent=w))
 		else:
-			ret = " ".join(get_words_from_user("Enter %s: " % desc))
+			ret = ' '.join(get_words_from_user('Enter %s: ' % desc))
 		self.ssdata.passwd = ret
 
 	def _get_first_pw_and_hp_and_encrypt_seed(self):
@@ -338,12 +336,12 @@ an empty passphrase, just hit ENTER twice.
 			old_pw = self.ss_in.ssdata.passwd
 			if opt.keep_passphrase:
 				d.passwd = old_pw
-				qmsg("Reusing passphrase at user request")
+				qmsg('Reusing passphrase at user request')
 			else:
 				pw = self._get_new_passphrase()
-				if self.op == "pwchg_new":
-					m = ("changed","unchanged")[int(pw==old_pw)]
-					qmsg("Passphrase %s" % m)
+				if self.op == 'pwchg_new':
+					m = ('changed','unchanged')[pw==old_pw]
+					qmsg('Passphrase %s' % m)
 		else:
 			qmsg(self.msg['choose_passphrase'] % (self.desc,d.hash_preset))
 			self._get_new_passphrase()
@@ -357,12 +355,12 @@ an empty passphrase, just hit ENTER twice.
 class Mnemonic (SeedSourceUnenc):
 
 	stdin_ok = True
-	fmt_codes = "mmwords","words","mnemonic","mnem","mn","m"
-	desc = "mnemonic data"
-	ext = "mmwords"
+	fmt_codes = 'mmwords','words','mnemonic','mnem','mn','m'
+	desc = 'mnemonic data'
+	ext = 'mmwords'
 	wl_checksums = {
-		"electrum": '5ca31424',
-		"tirosh":   '1a5faeff'
+		'electrum': '5ca31424',
+		'tirosh':   '1a5faeff'
 	}
 	mn_base = 1626
 	wordlists = sorted(wl_checksums)
@@ -377,7 +375,7 @@ class Mnemonic (SeedSourceUnenc):
 	def baseNtohex(base,words,wl,pad=0):
 		deconv =  [wl.index(words[::-1][i])*(base**i)
 					for i in range(len(words))]
-		ret = ("{:0%sx}" % pad).format(sum(deconv))
+		ret = ('{:0%sx}' % pad).format(sum(deconv))
 		return ('','0')[len(ret) % 2] + ret
 
 	@staticmethod
@@ -402,28 +400,28 @@ class Mnemonic (SeedSourceUnenc):
 	def get_wordlist(cls,wordlist=None):
 		wordlist = wordlist or g.default_wordlist
 		if wordlist not in cls.wordlists:
-			die(1,'"%s": invalid wordlist.  Valid choices: %s' %
-				(wordlist,'"'+'" "'.join(cls.wordlists)+'"'))
+			die(1,"'%s': invalid wordlist.  Valid choices: '%s'" %
+				(wordlist,"' '".join(cls.wordlists)))
 
-		return __import__("mmgen.mn_"+wordlist,fromlist=["words"]).words.split()
+		return __import__('mmgen.mn_'+wordlist,fromlist=['words']).words.split()
 
 	@classmethod
 	def check_wordlist(cls,wlname):
 
 		wl = cls.get_wordlist(wlname)
-		Msg("Wordlist: %s\nLength: %i words" % (capfirst(wlname),len(wl)))
-		new_chksum = sha256(" ".join(wl)).hexdigest()[:8]
+		Msg('Wordlist: %s\nLength: %i words' % (capfirst(wlname),len(wl)))
+		new_chksum = sha256(' '.join(wl)).hexdigest()[:8]
 
 		if (sorted(wl) == wl):
-			Msg("List is sorted")
+			Msg('List is sorted')
 		else:
-			die(3,"ERROR: List is not sorted!")
+			die(3,'ERROR: List is not sorted!')
 
 		compare_chksums(
-			new_chksum,"generated checksum",
-			cls.wl_checksums[wlname],"saved checksum",
+			new_chksum,'generated checksum',
+			cls.wl_checksums[wlname],'saved checksum',
 			die_on_fail=True)
-		Msg("Checksum %s matches" % new_chksum)
+		Msg('Checksum %s matches' % new_chksum)
 
 	def _format(self):
 		wl = self.get_wordlist()
@@ -432,11 +430,11 @@ class Mnemonic (SeedSourceUnenc):
 
 		ret = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
 		# Internal error, so just die on fail
-		compare_or_die(ret,"recomputed seed",
-						seed_hex,"original",e="Internal error")
+		compare_or_die(ret,'recomputed seed',
+						seed_hex,'original',e='Internal error')
 
 		self.ssdata.mnemonic = mn
-		self.fmt_data = " ".join(mn) + "\n"
+		self.fmt_data = ' '.join(mn) + '\n'
 
 	def _deformat(self):
 
@@ -444,13 +442,13 @@ class Mnemonic (SeedSourceUnenc):
 		wl = self.get_wordlist()
 
 		if len(mn) not in g.mn_lens:
-			msg("Invalid mnemonic (%i words).  Allowed numbers of words: %s" %
-					(len(mn),", ".join([str(i) for i in g.mn_lens])))
+			msg('Invalid mnemonic (%i words).  Allowed numbers of words: %s' %
+					(len(mn),', '.join([str(i) for i in g.mn_lens])))
 			return False
 
 		for n,w in enumerate(mn,1):
 			if w not in wl:
-				msg("Invalid mnemonic: word #%s is not in the wordlist" % n)
+				msg('Invalid mnemonic: word #%s is not in the wordlist' % n)
 				return False
 
 		seed_hex = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
@@ -458,8 +456,8 @@ class Mnemonic (SeedSourceUnenc):
 		ret = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
 
 		# Internal error, so just die
-		compare_or_die(" ".join(ret),"recomputed mnemonic",
-						" ".join(mn),"original",e="Internal error")
+		compare_or_die(' '.join(ret),'recomputed mnemonic',
+						' '.join(mn),'original',e='Internal error')
 
 		self.seed = Seed(unhexlify(seed_hex))
 		self.ssdata.mnemonic = mn
@@ -469,21 +467,21 @@ class Mnemonic (SeedSourceUnenc):
 		return True
 
 	def _filename(self):
-		return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext)
+		return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext)
 
 
 class SeedFile (SeedSourceUnenc):
 
 	stdin_ok = True
-	fmt_codes = "mmseed","seed","s"
-	desc = "seed data"
-	ext = "mmseed"
+	fmt_codes = 'mmseed','seed','s'
+	desc = 'seed data'
+	ext = 'mmseed'
 
 	def _format(self):
 		b58seed = b58encode_pad(self.seed.data)
 		self.ssdata.chksum = make_chksum_6(b58seed)
 		self.ssdata.b58seed = b58seed
-		self.fmt_data = "%s %s\n" % (
+		self.fmt_data = '%s %s\n' % (
 				self.ssdata.chksum,
 				split_into_cols(4,b58seed)
 			)
@@ -493,10 +491,10 @@ class SeedFile (SeedSourceUnenc):
 		ld = self.fmt_data.split()
 
 		if not (7 <= len(ld) <= 12): # 6 <= padded b58 data (ld[1:]) <= 11
-			msg("Invalid data length (%s) in %s" % (len(ld),desc))
+			msg('Invalid data length (%s) in %s' % (len(ld),desc))
 			return False
 
-		a,b = ld[0],"".join(ld[1:])
+		a,b = ld[0],''.join(ld[1:])
 
 		if not is_chksum_6(a):
 			msg("'%s': invalid checksum format in %s" % (a, desc))
@@ -506,16 +504,16 @@ class SeedFile (SeedSourceUnenc):
 			msg("'%s': not a base 58 string, in %s" % (b, desc))
 			return False
 
-		vmsg_r("Validating %s checksum..." % desc)
+		vmsg_r('Validating %s checksum...' % desc)
 
 		if not compare_chksums(
-				a,"checksum",make_chksum_6(b),"base 58 data"):
+				a,'checksum',make_chksum_6(b),'base 58 data'):
 			return False
 
 		ret = b58decode_pad(b)
 
 		if ret == False:
-			msg("Invalid base-58 encoded seed: %s" % val)
+			msg('Invalid base-58 encoded seed: %s' % val)
 			return False
 
 		self.seed = Seed(ret)
@@ -527,27 +525,27 @@ class SeedFile (SeedSourceUnenc):
 		return True
 
 	def _filename(self):
-		return "%s[%s].%s" % (self.seed.sid,self.seed.length,self.ext)
+		return '%s[%s].%s' % (self.seed.sid,self.seed.length,self.ext)
 
 
 class Wallet (SeedSourceEnc):
 
-	fmt_codes = "wallet","w"
-	desc = g.proj_name + " wallet"
-	ext = "mmdat"
+	fmt_codes = 'wallet','w'
+	desc = g.proj_name + ' wallet'
+	ext = 'mmdat'
 
-	def _get_label_from_user(self,old_lbl=""):
-		d = ("to reuse the label '%s'" % old_lbl) if old_lbl else "for no label"
-		p = "Enter a wallet label, or hit ENTER %s: " % d
+	def _get_label_from_user(self,old_lbl=''):
+		d = ("to reuse the label '%s'" % old_lbl) if old_lbl else 'for no label'
+		p = 'Enter a wallet label, or hit ENTER %s: ' % d
 		while True:
 			ret = my_raw_input(p)
 			if ret:
 				if is_mmgen_wallet_label(ret):
 					self.ssdata.label = ret; return ret
 				else:
-					msg("Invalid label.  Trying again...")
+					msg('Invalid label.  Trying again...')
 			else:
-				ret = old_lbl or "No Label"
+				ret = old_lbl or 'No Label'
 				self.ssdata.label = ret; return ret
 
 	# nearly identical to _get_hash_preset() - factor?
@@ -563,9 +561,9 @@ class Wallet (SeedSourceEnc):
 			else: # Prompt, using old value as default
 				lbl = self._get_label_from_user(old_lbl)
 
-			if (not opt.keep_label) and self.op == "pwchg_new":
-				m = ("changed to '%s'" % lbl,"unchanged")[int(lbl==old_lbl)]
-				qmsg("Label %s" % m)
+			if (not opt.keep_label) and self.op == 'pwchg_new':
+				m = ("changed to '%s'" % lbl,'unchanged')[lbl==old_lbl]
+				qmsg('Label %s' % m)
 		elif opt.label:
 			qmsg("Using label '%s' requested on command line" % opt.label)
 			self.ssdata.label = opt.label
@@ -576,7 +574,7 @@ class Wallet (SeedSourceEnc):
 		self._get_first_pw_and_hp_and_encrypt_seed()
 		self._get_label()
 		d = self.ssdata
-		d.pw_status = ("NE","E")[int(len(d.passwd)==0)]
+		d.pw_status = ('NE','E')[len(d.passwd)==0]
 		d.timestamp = make_timestamp()
 
 	def _format(self):
@@ -586,32 +584,32 @@ class Wallet (SeedSourceEnc):
 		es_fmt = b58encode_pad(d.enc_seed)
 		lines = (
 			d.label,
-			"{} {} {} {} {}".format(s.sid.lower(), d.key_id.lower(),
+			'{} {} {} {} {}'.format(s.sid.lower(), d.key_id.lower(),
 										s.length, d.pw_status, d.timestamp),
-			"{}: {} {} {}".format(d.hash_preset,*get_hash_params(d.hash_preset)),
-			"{} {}".format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)),
-			"{} {}".format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt))
+			'{}: {} {} {}'.format(d.hash_preset,*get_hash_params(d.hash_preset)),
+			'{} {}'.format(make_chksum_6(slt_fmt),split_into_cols(4,slt_fmt)),
+			'{} {}'.format(make_chksum_6(es_fmt), split_into_cols(4,es_fmt))
 		)
-		chksum = make_chksum_6(" ".join(lines))
-		self.fmt_data = "%s\n" % "\n".join((chksum,)+lines)
+		chksum = make_chksum_6(' '.join(lines))
+		self.fmt_data = '%s\n' % '\n'.join((chksum,)+lines)
 
 	def _deformat(self):
 
 		def check_master_chksum(lines,desc):
 
 			if len(lines) != 6:
-				msg("Invalid number of lines (%s) in %s data" %
+				msg('Invalid number of lines (%s) in %s data' %
 						(len(lines),desc))
 				return False
 
 			if not is_chksum_6(lines[0]):
-				msg("Incorrect master checksum (%s) in %s data" %
+				msg('Incorrect master checksum (%s) in %s data' %
 						(lines[0],desc))
 				return False
 
-			chk = make_chksum_6(" ".join(lines[1:]))
-			if not compare_chksums(lines[0],"master",chk,"computed",
-						hdr="For wallet master checksum"):
+			chk = make_chksum_6(' '.join(lines[1:]))
+			if not compare_chksums(lines[0],'master',chk,'computed',
+						hdr='For wallet master checksum'):
 				return False
 
 			return True
@@ -641,26 +639,26 @@ class Wallet (SeedSourceEnc):
 
 		if hash_params != get_hash_params(d.hash_preset):
 			msg("Hash parameters '%s' don't match hash preset '%s'" %
-					(" ".join(hash_params), d.hash_preset))
+					(' '.join(hash_params), d.hash_preset))
 			return False
 
 		lmin,lmax = b58_lens[0],b58_lens[-1] # 22,33,44
-		for i,key in (4,"salt"),(5,"enc_seed"):
-			l = lines[i].split(" ")
+		for i,key in (4,'salt'),(5,'enc_seed'):
+			l = lines[i].split(' ')
 			chk = l.pop(0)
-			b58_val = "".join(l)
+			b58_val = ''.join(l)
 
 			if len(b58_val) < lmin or len(b58_val) > lmax:
-				msg("Invalid format for %s in %s: %s" % (key,self.desc,l))
+				msg('Invalid format for %s in %s: %s' % (key,self.desc,l))
 				return False
 
 			if not compare_chksums(chk,key,
-					make_chksum_6(b58_val),"computed checksum"):
+					make_chksum_6(b58_val),'computed checksum'):
 				return False
 
 			val = b58decode_pad(b58_val)
 			if val == False:
-				msg("Invalid base 58 number: %s" % b58_val)
+				msg('Invalid base 58 number: %s' % b58_val)
 				return False
 
 			setattr(d,key,val)
@@ -670,7 +668,7 @@ class Wallet (SeedSourceEnc):
 	def _decrypt(self):
 		d = self.ssdata
 		# Needed for multiple transactions with {}-txsign
-		suf = ("",self.infile.name)[int(bool(opt.quiet))]
+		suf = ('',self.infile.name)[bool(opt.quiet)]
 		self._get_passphrase(desc_suf=suf)
 		key = make_key(d.passwd, d.salt, d.hash_preset)
 		ret = decrypt_seed(d.enc_seed, key, d.seed_id, d.key_id)
@@ -681,7 +679,7 @@ class Wallet (SeedSourceEnc):
 			return False
 
 	def _filename(self):
-		return "{}-{}[{},{}].{}".format(
+		return '{}-{}[{},{}].{}'.format(
 				self.seed.sid,
 				self.ssdata.key_id,
 				self.seed.length,
@@ -689,80 +687,48 @@ class Wallet (SeedSourceEnc):
 				self.ext
 			)
 
-# 	def __str__(self):
-##	label,metadata,hash_preset,salt,enc_seed):
-# 		d = self.ssdata
-# 		s = self.seed
-# 		out = ["WALLET DATA"]
-# 		fs = "  {:18} {}"
-# 		pw_empty = "Yes" if d.metadata[3] == "E" else "No"
-# 		for i in (
-# 			("Label:",         d.label),
-# 			("Seed ID:",       s.sid),
-# 			("Key  ID:",       d.key_id),
-# 			("Seed length:",   "%s bits (%s bytes)" % (s.length,s.length/8)),
-# 			("Scrypt params:", "Preset '%s' (%s)" % (opt.hash_preset,
-# 					" ".join([str(i) for i in get_hash_params(opt.hash_preset)])
-# 					)
-# 			),
-# 			("Passphrase empty?", pw_empty),
-# 			("Timestamp:",     "%s UTC" % d.metadata[4]),
-# 		): out.append(fs.format(*i))
-#
-# 		fs = "  {:6} {}"
-# 		for i in (
-# 			("Salt:",   ""),
-# 			("  b58:",  b58encode_pad(d.salt)),
-# 			("  hex:",  hexlify(d.salt)),
-# 			("Encrypted seed:", ""),
-# 			("  b58:",  b58encode_pad(d.enc_seed)),
-# 			("  hex:",  hexlify(d.enc_seed))
-# 		): out.append(fs.format(*i))
-#
-# 		return "\n".join(out)
-
 
 class Brainwallet (SeedSourceEnc):
 
 	stdin_ok = True
-	fmt_codes = "mmbrain","brainwallet","brain","bw","b"
-	desc = "brainwallet"
-	ext = "mmbrain"
+	fmt_codes = 'mmbrain','brainwallet','brain','bw','b'
+	desc = 'brainwallet'
+	ext = 'mmbrain'
 	# brainwallet warning message? TODO
 
 	def get_bw_params(self):
 		# already checked
-		a = opt.brain_params.split(",")
+		a = opt.brain_params.split(',')
 		return int(a[0]),a[1]
 
 	def _deformat(self):
-		self.brainpasswd = " ".join(self.fmt_data.split())
+		self.brainpasswd = ' '.join(self.fmt_data.split())
 		return True
 
 	def _decrypt(self):
 		d = self.ssdata
-		if hasattr(opt,"brain_params"):
+		if hasattr(opt,'brain_params'):
 			seed_len,d.hash_preset = self.get_bw_params()
 		else:
 			self._get_hash_preset()
 			seed_len = opt.seed_len
-		vmsg_r("Hashing brainwallet data.  Please wait...")
+		vmsg_r('Hashing brainwallet data.  Please wait...')
 		# Use buflen arg of scrypt.hash() to get seed of desired length
-		seed = scrypt_hash_passphrase(self.brainpasswd, "",
+		seed = scrypt_hash_passphrase(self.brainpasswd, '',
 					d.hash_preset, buflen=seed_len/8)
-		vmsg("Done")
+		vmsg('Done')
 		self.seed = Seed(seed)
-		msg("Seed ID: %s" % self.seed.sid)
-		qmsg("Check this value against your records")
+		msg('Seed ID: %s' % self.seed.sid)
+		qmsg('Check this value against your records')
 		return True
 
 
 class IncogWallet (SeedSourceEnc):
 
-	file_mode = "binary"
-	fmt_codes = "mmincog","incog","icg","i"
-	desc = "incognito data"
-	ext = "mmincog"
+	file_mode = 'binary'
+	fmt_codes = 'mmincog','incog','icg','i'
+	desc = 'incognito data'
+	ext = 'mmincog'
 	no_tty = True
 
 	_msg = {
@@ -788,7 +754,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 	def _make_iv_chksum(self,s): return sha256(s).hexdigest()[:8].upper()
 
 	def _get_incog_data_len(self,seed_len):
-		e = (g.hincog_chk_len,0)[int(bool(opt.old_incog_fmt))]
+		e = (g.hincog_chk_len,0)[bool(opt.old_incog_fmt)]
 		return g.aesctr_iv_len + g.salt_len + e + seed_len/8
 
 	def _incog_data_size_chk(self):
@@ -799,38 +765,38 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 			return True
 		else:
 			if opt.old_incog_fmt:
-				msg("WARNING: old-style incognito format requested.  " +
-					"Are you sure this is correct?")
-			msg(("Invalid incognito data size (%s bytes) for this " +
-				"seed length (%s bits)") % (dlen,opt.seed_len))
-			msg("Valid data size for this seed length: %s bytes" % valid_dlen)
+				msg('WARNING: old-style incognito format requested.  ' +
+					'Are you sure this is correct?')
+			msg(('Invalid incognito data size (%s bytes) for this ' +
+				'seed length (%s bits)') % (dlen,opt.seed_len))
+			msg('Valid data size for this seed length: %s bytes' % valid_dlen)
 			for sl in g.seed_lens:
 				if dlen == self._get_incog_data_len(sl):
-					die(1,"Valid seed length for this data size: %s bits" % sl)
-			msg(("This data size (%s bytes) is invalid for all available " +
-				"seed lengths") % dlen)
+					die(1,'Valid seed length for this data size: %s bits' % sl)
+			msg(('This data size (%s bytes) is invalid for all available ' +
+				'seed lengths') % dlen)
 			return False
 
 	def _encrypt (self):
 		self._get_first_pw_and_hp_and_encrypt_seed()
 		if opt.old_incog_fmt:
-			die(1,"Writing old-format incog wallets is unsupported")
+			die(1,'Writing old-format incog wallets is unsupported')
 		d = self.ssdata
 		# IV is used BOTH to initialize counter and to salt password!
 		d.iv = get_random(g.aesctr_iv_len)
 		d.iv_id = self._make_iv_chksum(d.iv)
-		msg("New Incog Wallet ID: %s" % d.iv_id)
-		qmsg("Make a record of this value")
+		msg('New Incog Wallet ID: %s' % d.iv_id)
+		qmsg('Make a record of this value')
 		vmsg(self.msg['record_incog_id'])
 
 		d.salt = get_random(g.salt_len)
-		key = make_key(d.passwd, d.salt, d.hash_preset, "incog wallet key")
+		key = make_key(d.passwd, d.salt, d.hash_preset, 'incog wallet key')
 		chk = sha256(self.seed.data).digest()[:8]
-		d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, "seed")
+		d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, 'seed')
 
-		d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, "incog wrapper key")
+		d.wrapper_key = make_key(d.passwd, d.iv, d.hash_preset, 'incog wrapper key')
 		d.key_id = make_chksum_8(d.wrapper_key)
-		vmsg("Key ID: %s" % d.key_id)
+		vmsg('Key ID: %s' % d.key_id)
 		d.target_data_len = self._get_incog_data_len(self.seed.length)
 
 	def _format(self):
@@ -845,7 +811,7 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 	def _filename(self):
 		s = self.seed
 		d = self.ssdata
-		return "{}-{}-{}[{},{}].{}".format(
+		return '{}-{}-{}[{},{}].{}'.format(
 				s.sid,
 				d.key_id,
 				d.iv_id,
@@ -862,8 +828,8 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 		d.iv             = self.fmt_data[0:g.aesctr_iv_len]
 		d.incog_id       = self._make_iv_chksum(d.iv)
 		d.enc_incog_data = self.fmt_data[g.aesctr_iv_len:]
-		msg("Incog Wallet ID: %s" % d.incog_id)
-		qmsg("Check this value against your records")
+		msg('Incog Wallet ID: %s' % d.incog_id)
+		qmsg('Check this value against your records')
 		vmsg(self.msg['check_incog_id'])
 
 		return True
@@ -871,14 +837,14 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 	def _verify_seed_newfmt(self,data):
 		chk,seed = data[:8],data[8:]
 		if sha256(seed).digest()[:8] == chk:
-			qmsg("Passphrase%s are correct" % (self.msg['dec_chk'] % "and"))
+			qmsg('Passphrase%s are correct' % (self.msg['dec_chk'] % 'and'))
 			return seed
 		else:
-			msg("Incorrect passphrase%s" % (self.msg['dec_chk'] % "or"))
+			msg('Incorrect passphrase%s' % (self.msg['dec_chk'] % 'or'))
 			return False
 
 	def _verify_seed_oldfmt(self,seed):
-		m = "Seed ID: %s.  Is the Seed ID correct?" % make_chksum_8(seed)
+		m = 'Seed ID: %s.  Is the Seed ID correct?' % make_chksum_8(seed)
 		if keypress_confirm(m, True):
 			return seed
 		else:
@@ -890,24 +856,24 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 		self._get_passphrase(desc_suf=d.incog_id)
 
 		# IV is used BOTH to initialize counter and to salt password!
-		key = make_key(d.passwd, d.iv, d.hash_preset, "wrapper key")
+		key = make_key(d.passwd, d.iv, d.hash_preset, 'wrapper key')
 		dd = decrypt_data(d.enc_incog_data, key,
-				int(hexlify(d.iv),16), "incog data")
+				int(hexlify(d.iv),16), 'incog data')
 
 		d.salt     = dd[0:g.salt_len]
 		d.enc_seed = dd[g.salt_len:]
 
-		key = make_key(d.passwd, d.salt, d.hash_preset, "main key")
-		qmsg("Key ID: %s" % make_chksum_8(key))
+		key = make_key(d.passwd, d.salt, d.hash_preset, 'main key')
+		qmsg('Key ID: %s' % make_chksum_8(key))
 
-		verify_seed = getattr(self,"_verify_seed_"+
-						("newfmt","oldfmt")[int(bool(opt.old_incog_fmt))])
+		verify_seed = getattr(self,'_verify_seed_'+
+						('newfmt','oldfmt')[bool(opt.old_incog_fmt)])
 
-		seed = verify_seed(decrypt_seed(d.enc_seed, key, "", ""))
+		seed = verify_seed(decrypt_seed(d.enc_seed, key, '', ''))
 
 		if seed:
 			self.seed = Seed(seed)
-			msg("Seed ID: %s" % self.seed.sid)
+			msg('Seed ID: %s' % self.seed.sid)
 			return True
 		else:
 			return False
@@ -915,10 +881,10 @@ to exit and re-run the program with the '--old-incog-fmt' option.
 
 class IncogWalletHex (IncogWallet):
 
-	file_mode = "text"
-	desc = "hex incognito data"
-	fmt_codes = "mmincox","incox","incog_hex","xincog","ix","xi"
-	ext = "mmincox"
+	file_mode = 'text'
+	desc = 'hex incognito data'
+	fmt_codes = 'mmincox','incox','incog_hex','xincog','ix','xi'
+	ext = 'mmincox'
 	no_tty = False
 
 	def _deformat(self):
@@ -936,8 +902,8 @@ class IncogWalletHex (IncogWallet):
 
 class IncogWalletHidden (IncogWallet):
 
-	desc = "hidden incognito data"
-	fmt_codes = "incog_hidden","hincog","ih","hi"
+	desc = 'hidden incognito data'
+	fmt_codes = 'incog_hidden','hincog','ih','hi'
 	ext = None
 
 	_msg = {
@@ -957,18 +923,18 @@ harder to find, you're advised to choose a much larger file size than this.
   identify the incog wallet data in the future and to locate the offset
   where the data is hidden in the event you forget it.
 	""",
-		'dec_chk': ", hash preset, offset %s seed length"
+		'dec_chk': ', hash preset, offset %s seed length'
 	}
 
 
 	def _get_hincog_params(self,wtype):
 		p = getattr(opt,'hidden_incog_'+ wtype +'_params')
-		a,b = p.split(",")
+		a,b = p.split(',')
 		return a,int(b)
 
 	def _check_valid_offset(self,fn,action):
 		d = self.ssdata
-		m = ("Input","Destination")[int(action=="write")]
+		m = ('Input','Destination')[action=='write']
 		if fn.size < d.hincog_offset + d.target_data_len:
 			die(1,
 	"%s file '%s' has length %s, too short to %s %s bytes of data at offset %s"
@@ -976,29 +942,29 @@ harder to find, you're advised to choose a much larger file size than this.
 
 	def _get_data(self):
 		d = self.ssdata
-		d.hincog_offset = self._get_hincog_params("input")[1]
+		d.hincog_offset = self._get_hincog_params('input')[1]
 
 		qmsg("Getting hidden incog data from file '%s'" % self.infile.name)
 
 		# Already sanity-checked:
 		d.target_data_len = self._get_incog_data_len(opt.seed_len)
-		self._check_valid_offset(self.infile,"read")
+		self._check_valid_offset(self.infile,'read')
 
 		fh = os.open(self.infile.name,os.O_RDONLY)
 		os.lseek(fh,int(d.hincog_offset),os.SEEK_SET)
 		self.fmt_data = os.read(fh,d.target_data_len)
 		os.close(fh)
 		qmsg("Data read from file '%s' at offset %s" %
-				(self.infile.name,d.hincog_offset), "Data read from file")
+				(self.infile.name,d.hincog_offset), 'Data read from file')
 
 	# overrides method in SeedSource
 	def write_to_file(self):
 		d = self.ssdata
 		self._format()
-		compare_or_die(d.target_data_len, "target data length",
-				len(self.fmt_data),"length of formatted " + self.desc)
+		compare_or_die(d.target_data_len, 'target data length',
+				len(self.fmt_data),'length of formatted ' + self.desc)
 
-		k = ("output","input")[int(self.op=="pwchg_new")]
+		k = ('output','input')[self.op=='pwchg_new']
 		fn,d.hincog_offset = self._get_hincog_params(k)
 
 		check_offset = True
@@ -1010,25 +976,25 @@ harder to find, you're advised to choose a much larger file size than this.
 				min_fsize = d.target_data_len + d.hincog_offset
 				msg(self.msg['choose_file_size'].format(min_fsize))
 				while True:
-					fsize = parse_nbytes(my_raw_input("Enter file size: "))
+					fsize = parse_nbytes(my_raw_input('Enter file size: '))
 					if fsize >= min_fsize: break
-					msg("File size must be an integer no less than %s" %
+					msg('File size must be an integer no less than %s' %
 							min_fsize)
 
 				from mmgen.tool import rand2file
 				rand2file(fn, str(fsize))
 				check_offset = False
 			else:
-				die(1,"Exiting at user request")
+				die(1,'Exiting at user request')
 
 		self.outfile = f = Filename(fn,ftype=self.fmt_codes[0],write=True)
 
-		dmsg("%s data len %s, offset %s" % (
+		dmsg('%s data len %s, offset %s' % (
 				capfirst(self.desc),d.target_data_len,d.hincog_offset))
 
 		if check_offset:
-			self._check_valid_offset(f,"write")
-			if not opt.quiet: confirm_or_exit("","alter file '%s'" % f.name)
+			self._check_valid_offset(f,'write')
+			if not opt.quiet: confirm_or_exit('',"alter file '%s'" % f.name)
 
 		fh = os.open(f.name,os.O_RDWR)
 		os.lseek(fh, int(d.hincog_offset), os.SEEK_SET)

+ 36 - 32
mmgen/share/Opts.py

@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-# Opts.py, an options parsing library for Python.  Copyright (C) 2014 by
+# Opts.py, an options parsing library for Python.  Copyright (C) 2014-2016
 # Philemon <mmgen-py@yandex.com>.
 #
 # This program is free software: you can redistribute it and/or modify
@@ -16,28 +16,32 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+"""
+Opts.py:  Generic options handling
+"""
+
 import sys, getopt
 
 def usage(opts_data):
-	print "USAGE: %s %s" % (opts_data['prog_name'], opts_data['usage'])
+	print 'USAGE: %s %s' % (opts_data['prog_name'], opts_data['usage'])
 	sys.exit(2)
 
 def print_help(opts_data):
 	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+"%s" % sep.join(opts_data['options'].strip().splitlines())
-	if "notes" in opts_data:
-		print "  %s" % "\n  ".join(opts_data['notes'][1:-1].splitlines())
+	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:
+		print '  ' + '\n  '.join(opts_data['notes'][1:-1].splitlines())
 
 
 def process_opts(argv,opts_data,short_opts,long_opts):
 
 	import os
 	opts_data['prog_name'] = os.path.basename(sys.argv[0])
-	long_opts  = [i.replace("_","-") for i in long_opts]
+	long_opts  = [i.replace('_','-') for i in long_opts]
 
 	try: cl_opts,args = getopt.getopt(argv[1:], short_opts, long_opts)
 	except getopt.GetoptError as err:
@@ -46,21 +50,21 @@ def process_opts(argv,opts_data,short_opts,long_opts):
 	opts,short_opts_l = {},[]
 
 	for i in short_opts:
-		if i == ":": short_opts_l[-1] += i
+		if i == ':': short_opts_l[-1] += i
 		else:        short_opts_l     += i
 
 	for opt, arg in cl_opts:
-		if   opt in ("-h","--help"): print_help(opts_data); 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:
-			opts[opt[2:].replace("-","_")] = arg
-		elif opt[0] == "-" and opt[1]     in short_opts_l:
-			opts[long_opts[short_opts_l.index(opt[1:])].replace("-","_")] = True
-		elif opt[0] == "-" and opt[1:]+":" in short_opts_l:
+		if   opt in ('-h','--help'): print_help(opts_data); 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:
+			opts[opt[2:].replace('-','_')] = arg
+		elif opt[0] == '-' and opt[1]     in short_opts_l:
+			opts[long_opts[short_opts_l.index(opt[1:])].replace('-','_')] = True
+		elif opt[0] == '-' and opt[1:]+':' in short_opts_l:
 			opts[long_opts[short_opts_l.index(
-					opt[1:]+":")][:-1].replace("-","_")] = arg
-		else: assert False, "Invalid option"
+					opt[1:]+':')][:-1].replace('-','_')] = arg
+		else: assert False, 'Invalid option'
 
 	if 'sets' in opts_data:
 		for o_in,v_in,o_out,v_out in opts_data['sets']:
@@ -69,9 +73,9 @@ def process_opts(argv,opts_data,short_opts,long_opts):
 				if (v and v_in == bool) or v == v_in:
 					if o_out in opts and opts[o_out] != v_out:
 						sys.stderr.write(
-				"Option conflict:\n  --%s=%s, with\n  --%s=%s\n" % (
-					o_out.replace("_","-"),opts[o_out],
-					o_in.replace("_","-"),opts[o_in]
+				'Option conflict:\n  --%s=%s, with\n  --%s=%s\n' % (
+					o_out.replace('_','-'),opts[o_out],
+					o_in.replace('_','-'),opts[o_in]
 				))
 						sys.exit(1)
 					else:
@@ -83,24 +87,24 @@ def process_opts(argv,opts_data,short_opts,long_opts):
 def parse_opts(argv,opts_data,opt_filter=None):
 
 	import re
-	pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{2,64})(=| )(.+)"
+	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 = True if (opt_filter and m.group(1) not in opt_filter) else False
-			app = [':','='] if (m.group(3) == '=') else ['','']
+			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
+			if not skip: od[-1][3] += '\n' + l
 
-	opts_data['options'] = "\n".join(
-		["-{}, --{} {}".format(d[0],d[1],d[3]) for d in od if d[6] == False]
+	opts_data['options'] = '\n'.join(
+		['-{}, --{} {}'.format(d[0],d[1],d[3]) for d in od if d[6] == False]
 	)
-	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]
+	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]
 
 	opts,args = process_opts(argv,opts_data,short_opts,long_opts)
 

+ 1 - 1
mmgen/share/__init__.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013-2015 by philemon <mmgen-py@yandex.com>
+# Copyright (C) 2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by

+ 29 - 30
mmgen/term.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,10 +20,9 @@
 term.py:  Terminal-handling routines for the MMGen suite
 """
 
-import sys, os, struct
-import mmgen.globalvars as g
-import opt
-from mmgen.util import msg, msg_r
+import os,struct
+
+from mmgen.common import *
 
 def _kb_hold_protect_unix():
 
@@ -42,7 +41,7 @@ def _kb_hold_protect_unix():
 
 def _kb_hold_protect_unix_raw(): pass
 
-def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
+def _get_keypress_unix(prompt='',immed_chars='',prehold_protect=True):
 
 	msg_r(prompt)
 	timeout = float(0.3)
@@ -57,8 +56,8 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
 		ch = sys.stdin.read(1)
 		if prehold_protect:
 			if key: continue
-		if immed_chars == "ALL" or ch in immed_chars: break
-		if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": break
+		if immed_chars == 'ALL' or ch in immed_chars: break
+		if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r': break
 		# Protect against long keypress
 		key = select([sys.stdin], [], [], timeout)[0]
 		if not key: break
@@ -67,7 +66,7 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
 	return ch
 
 
-def _get_keypress_unix_raw(prompt="",immed_chars="",prehold_protect=None):
+def _get_keypress_unix_raw(prompt='',immed_chars='',prehold_protect=None):
 
 	msg_r(prompt)
 
@@ -98,7 +97,7 @@ def _kb_hold_protect_mswin():
 
 def _kb_hold_protect_mswin_raw(): pass
 
-def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
+def _get_keypress_mswin(prompt='',immed_chars='',prehold_protect=True):
 
 	msg_r(prompt)
 	timeout = float(0.5)
@@ -109,9 +108,9 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
 
 			if ord(ch) == 3: raise KeyboardInterrupt
 
-			if immed_chars == "ALL" or ch in immed_chars:
+			if immed_chars == 'ALL' or ch in immed_chars:
 				return ch
-			if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
+			if immed_chars == 'ALL_EXCEPT_ENTER' and not ch in '\n\r':
 				return ch
 
 			hit_time = time.time()
@@ -121,7 +120,7 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
 				if float(time.time() - hit_time) > timeout:
 					return ch
 
-def _get_keypress_mswin_raw(prompt="",immed_chars="",prehold_protect=None):
+def _get_keypress_mswin_raw(prompt='',immed_chars='',prehold_protect=None):
 
 	msg_r(prompt)
 	ch = msvcrt.getch()
@@ -160,7 +159,7 @@ def _get_terminal_size_linux():
 
 def _get_terminal_size_mswin():
 	try:
-		from ctypes import windll, create_string_buffer
+		from ctypes import windll,create_string_buffer
 		# stdin handle is -10
 		# stdout handle is -11
 		# stderr handle is -12
@@ -169,7 +168,7 @@ def _get_terminal_size_mswin():
 		res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
 		if res:
 			(bufx, bufy, curx, cury, wattr, left, top, right, bottom,
-			maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
+			maxx, maxy) = struct.unpack('hhhhHhhhhhh', csbi.raw)
 			sizex = right - left + 1
 			sizey = bottom - top + 1
 			return sizex, sizey
@@ -179,7 +178,7 @@ def _get_terminal_size_mswin():
 def mswin_dummy_flush(fd,termconst): pass
 
 try:
-	import tty, termios
+	import tty,termios
 	from select import select
 	if g.disable_hold_protect:
 		get_char = _get_keypress_unix_raw
@@ -192,7 +191,7 @@ try:
 # call: myflush(sys.stdin, termios.TCIOFLUSH)
 except:
 	try:
-		import msvcrt, time
+		import msvcrt,time
 		if g.disable_hold_protect:
 			get_char = _get_keypress_mswin_raw
 			kb_hold_protect = _kb_hold_protect_mswin_raw
@@ -202,18 +201,18 @@ except:
 		get_terminal_size = _get_terminal_size_mswin
 		myflush = mswin_dummy_flush
 	except:
-		if not sys.platform.startswith("linux") \
-				and not sys.platform.startswith("win"):
-			msg("Unsupported platform: %s" % sys.platform)
-			msg("This program currently runs only on Linux and Windows")
+		if not sys.platform.startswith('linux') \
+				and not sys.platform.startswith('win'):
+			msg('Unsupported platform: %s' % sys.platform)
+			msg('This program currently runs only on Linux and Windows')
 		else:
-			msg("Unable to set terminal mode")
+			msg('Unable to set terminal mode')
 		sys.exit(2)
 
 
 def do_pager(text):
 
-	pagers = ["less","more"]
+	pagers = ['less','more']
 	shell = False
 
 	from os import environ
@@ -223,23 +222,23 @@ def do_pager(text):
 # not found.
 # When 'shell' is false, an exception is raised, invoking the fallback
 # 'print' instead of the pager.
-# We risk assuming that "more" will always be available on a stock
+# We risk assuming that 'more' will always be available on a stock
 # Windows installation.
-	if sys.platform.startswith("win") and 'HOME' not in environ:
+	if sys.platform.startswith('win') and 'HOME' not in environ:
 		shell = True
-		pagers = ["more"]
+		pagers = ['more']
 
 	if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
 		pagers = [environ['PAGER']] + pagers
 
 	for pager in pagers:
-		end = "" if pager == "less" else "\n(end of text)\n"
+		end = ('\n(end of text)\n','')[pager=='less']
 		try:
-			from subprocess import Popen, PIPE, STDOUT
+			from subprocess import Popen,PIPE,STDOUT
 			p = Popen([pager], stdin=PIPE, shell=shell)
 		except: pass
 		else:
-			p.communicate(text+end+"\n")
-			msg_r("\r")
+			p.communicate(text+end+'\n')
+			msg_r('\r')
 			break
 	else: Msg(text+end)

+ 8 - 8
mmgen/test.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,10 +20,10 @@
 test.py:  Shared routines for the test suites
 """
 
-import sys,os
+import os
 from binascii import hexlify
-from mmgen.util import msg,write_data_to_file,red,green
-import mmgen.opt as opt
+
+from mmgen.common import *
 
 def cleandir(d):
 	try:    files = os.listdir(d)
@@ -38,7 +38,7 @@ def getrandhex(n): return hexlify(os.urandom(n))
 def getrandstr(num_chars,no_space=False):
 	n,m = 95,32
 	if no_space: n,m = 94,33
-	return "".join([chr(ord(i)%n+m) for i in list(os.urandom(num_chars))])
+	return ''.join([chr(ord(i)%n+m) for i in list(os.urandom(num_chars))])
 
 def mk_tmpdir(cfg):
 	try: os.mkdir(cfg['tmpdir'],0755)
@@ -66,8 +66,8 @@ def read_from_tmpfile(cfg,fn,binary=False):
 
 def ok():
 	if opt.verbose or opt.exact_output:
-		sys.stderr.write(green("OK\n"))
-	else: msg(" OK")
+		sys.stderr.write(green('OK\n'))
+	else: msg(' OK')
 
 def ok_or_die(val,chk_func,s,skip_ok=False):
 	try: ret = chk_func(val)
@@ -83,6 +83,6 @@ def cmp_or_die(s,t,skip_ok=False):
 		if not skip_ok: ok()
 	else:
 		sys.stderr.write(red(
-			"ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n" %
+			'ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n' %
 				(repr(t),repr(s))))
 		sys.exit(3)

+ 171 - 174
mmgen/tool.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,74 +20,71 @@
 tool.py:  Routines and data for the 'mmgen-tool' utility
 """
 
-import sys
-import mmgen.bitcoin as bitcoin
 import binascii as ba
 
-import mmgen.globalvars as g
-import mmgen.opt as opt
+import mmgen.bitcoin as bitcoin
+from mmgen.common import *
 from mmgen.crypto import *
-from mmgen.util import *
 from mmgen.tx import *
 
 pnm = g.proj_name
 
 from collections import OrderedDict
 cmd_data = OrderedDict([
-	("help",         ['<tool command> [str]']),
-	("usage",        ['<tool command> [str]']),
-	("strtob58",     ['<string> [str]']),
-	("b58tostr",     ['<b58 number> [str]']),
-	("hextob58",     ['<hex number> [str]']),
-	("b58tohex",     ['<b58 number> [str]']),
-	("b58randenc",   []),
-	("b32tohex",     ['<b32 num> [str]']),
-	("hextob32",     ['<hex num> [str]']),
-	("randhex",      ['nbytes [int=32]']),
-	("id8",          ['<infile> [str]']),
-	("id6",          ['<infile> [str]']),
-	("sha256x2",     ['<str, hexstr or filename> [str]',
+	('help',         ['<tool command> [str]']),
+	('usage',        ['<tool command> [str]']),
+	('strtob58',     ['<string> [str]']),
+	('b58tostr',     ['<b58 number> [str]']),
+	('hextob58',     ['<hex number> [str]']),
+	('b58tohex',     ['<b58 number> [str]']),
+	('b58randenc',   []),
+	('b32tohex',     ['<b32 num> [str]']),
+	('hextob32',     ['<hex num> [str]']),
+	('randhex',      ['nbytes [int=32]']),
+	('id8',          ['<infile> [str]']),
+	('id6',          ['<infile> [str]']),
+	('sha256x2',     ['<str, hexstr or filename> [str]',
 							'hex_input [bool=False]','file_input [bool=False]']),
-	("str2id6",      ['<string (spaces are ignored)> [str]']),
-	("hexdump",      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
-	("unhexdump",    ['<infile> [str]']),
-	("hexreverse",   ['<hexadecimal string> [str]']),
-	("hexlify",      ['<string> [str]']),
-	("rand2file",    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False']),
-
-	("randwif",    ['compressed [bool=False]']),
-	("randpair",   ['compressed [bool=False]']),
-	("hex2wif",    ['<private key in hex format> [str]', 'compressed [bool=False]']),
-	("wif2hex",    ['<wif> [str]', 'compressed [bool=False]']),
-	("wif2addr",   ['<wif> [str]', 'compressed [bool=False]']),
-	("hexaddr2addr", ['<btc address in hex format> [str]']),
-	("addr2hexaddr", ['<btc address> [str]']),
-	("pubkey2addr",  ['<public key in hex format> [str]']),
-	("pubkey2hexaddr", ['<public key in hex format> [str]']),
-	("privhex2addr", ['<private key in hex format> [str]','compressed [bool=False]']),
-
-	("hex2mn",       ['<hexadecimal string> [str]','wordlist [str="electrum"]']),
-	("mn2hex",       ['<mnemonic> [str]', 'wordlist [str="electrum"]']),
-	("mn_rand128",   ['wordlist [str="electrum"]']),
-	("mn_rand192",   ['wordlist [str="electrum"]']),
-	("mn_rand256",   ['wordlist [str="electrum"]']),
-	("mn_stats",     ['wordlist [str="electrum"]']),
-	("mn_printlist", ['wordlist [str="electrum"]']),
-
-
-	("listaddresses",['addrs [str='']','minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
-	("getbalance",   ['minconf [int=1]']),
-	("txview",       ['<{pnm} tx file> [str]','pager [bool=False]','terse [bool=False]'.format(pnm=pnm)]),
-
-	("add_label",       ['<{pnm} address> [str]'.format(pnm=pnm),'<label> [str]']),
-	("remove_label",    ['<{pnm} address> [str]'.format(pnm=pnm)]),
-	("addrfile_chksum", ['<{pnm} addr file> [str]'.format(pnm=pnm)]),
-	("keyaddrfile_chksum", ['<{pnm} addr file> [str]'.format(pnm=pnm)]),
-	("find_incog_data", ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
-
-	("encrypt",      ['<infile> [str]','outfile [str=""]','hash_preset [str=""]']),
-	("decrypt",      ['<infile> [str]','outfile [str=""]','hash_preset [str=""]']),
-	("bytespec",     ['<bytespec> [str]']),
+	('str2id6',      ['<string (spaces are ignored)> [str]']),
+	('hexdump',      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]']),
+	('unhexdump',    ['<infile> [str]']),
+	('hexreverse',   ['<hexadecimal string> [str]']),
+	('hexlify',      ['<string> [str]']),
+	('rand2file',    ['<outfile> [str]','<nbytes> [str]','threads [int=4]','silent [bool=False]']),
+
+	('randwif',    ['compressed [bool=False]']),
+	('randpair',   ['compressed [bool=False]']),
+	('hex2wif',    ['<private key in hex format> [str]', 'compressed [bool=False]']),
+	('wif2hex',    ['<wif> [str]', 'compressed [bool=False]']),
+	('wif2addr',   ['<wif> [str]', 'compressed [bool=False]']),
+	('hexaddr2addr', ['<btc address in hex format> [str]']),
+	('addr2hexaddr', ['<btc address> [str]']),
+	('pubkey2addr',  ['<public key in hex format> [str]']),
+	('pubkey2hexaddr', ['<public key in hex format> [str]']),
+	('privhex2addr', ['<private key in hex format> [str]','compressed [bool=False]']),
+
+	('hex2mn',       ['<hexadecimal string> [str]',"wordlist [str='electrum']"]),
+	('mn2hex',       ['<mnemonic> [str]', "wordlist [str='electrum']"]),
+	('mn_rand128',   ["wordlist [str='electrum']"]),
+	('mn_rand192',   ["wordlist [str='electrum']"]),
+	('mn_rand256',   ["wordlist [str='electrum']"]),
+	('mn_stats',     ["wordlist [str='electrum']"]),
+	('mn_printlist', ["wordlist [str='electrum']"]),
+
+
+	('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
+	('getbalance',   ['minconf [int=1]']),
+	('txview',       ['<{} TX file> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]']),
+
+	('add_label',       ['<{} address> [str]'.format(pnm),'<label> [str]']),
+	('remove_label',    ['<{} address> [str]'.format(pnm)]),
+	('addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
+	('keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
+	('find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
+
+	('encrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
+	('decrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
+	('bytespec',     ['<bytespec> [str]']),
 ])
 
 cmd_help = """
@@ -144,7 +141,7 @@ cmd_help = """
   id8          - generate 8-character {pnm} ID for a file (or stdin)
   str2id6      - generate 6-character {pnm} ID for a string, ignoring spaces
 
-  Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
+  Mnemonic operations (choose 'electrum' (default), 'tirosh' or 'all'
   wordlists):
   mn_rand128   - generate random 128-bit mnemonic
   mn_rand192   - generate random 192-bit mnemonic
@@ -159,36 +156,38 @@ cmd_help = """
 """.format(pnm=pnm)
 
 def tool_usage(prog_name, command):
-	Msg("USAGE: '%s %s%s'" % (prog_name, command,
-		(" "+" ".join(cmd_data[command]) if cmd_data[command] else "")))
+	if command in cmd_data:
+		Msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cmd_data[command])))
+	else:
+		Msg("'%s': no such tool command" % command)
 
 def process_args(prog_name, command, cmd_args):
-	c_args = [[i.split(" [")[0],i.split(" [")[1][:-1]]
-		for i in cmd_data[command] if "=" not in i]
+	c_args = [[i.split(' [')[0],i.split(' [')[1][:-1]]
+		for i in cmd_data[command] if '=' not in i]
 	c_kwargs = dict([[
-			i.split(" [")[0],
-			[i.split(" [")[1].split("=")[0], i.split(" [")[1].split("=")[1][:-1]]
-		] for i in cmd_data[command] if "=" in i])
+			i.split(' [')[0],
+			[i.split(' [')[1].split('=')[0], i.split(' [')[1].split('=')[1][:-1]]
+		] for i in cmd_data[command] if '=' in i])
 
 	u_args = cmd_args[:len(c_args)]
 	u_kwargs = cmd_args[len(c_args):]
 
 	if len(u_args) < len(c_args):
-		msg("%s arg%s required" % (len(c_args),suf(c_args,"k")))
+		msg('%s argument%s required' % (len(c_args),suf(c_args,'k')))
 		tool_usage(prog_name, command)
 		sys.exit(1)
 
 	if len(u_kwargs) > len(c_kwargs):
-		msg("Too many arguments")
+		msg('Too many arguments')
 		tool_usage(prog_name, command)
 		sys.exit(1)
 
-	u_kwargs = dict([a.split("=") for a in u_kwargs])
+	u_kwargs = dict([a.split('=') for a in u_kwargs])
 
 #	print c_args; print c_kwargs; print u_args; print u_kwargs; sys.exit()
 
 	if set(u_kwargs) > set(c_kwargs):
-		die(1,"Invalid named argument")
+		die(1,'Invalid named argument')
 
 	def convert_type(arg,arg_name,arg_type):
 		try:
@@ -198,9 +197,9 @@ def process_args(prog_name, command, cmd_args):
 				(arg, arg_name, arg_type))
 
 	def convert_to_bool_maybe(arg, arg_type):
-		if arg_type == "bool":
-			if arg.lower() in ("true","yes","1","on"): return True
-			if arg.lower() in ("false","no","0","off"): return False
+		if arg_type == 'bool':
+			if arg.lower() in ('true','yes','1','on'): return True
+			if arg.lower() in ('false','no','0','off'): return False
 		return arg
 
 	args = []
@@ -220,24 +219,24 @@ def process_args(prog_name, command, cmd_args):
 # Individual cmd_data
 
 # def help():
-# 	Msg("Available commands:")
+# 	Msg('Available commands:')
 # 	for k in sorted(cmd_data.keys()):
-# 		Msg("%-16s %s" % (k," ".join(cmd_data[k])))
+# 		Msg('%-16s %s' % (k,' '.join(cmd_data[k])))
 
-def are_equal(a,b,dtype=""):
-	if dtype == "str": return a.lstrip("\0") == b.lstrip("\0")
-	if dtype == "hex": return a.lstrip("0") == b.lstrip("0")
-	if dtype == "b58": return a.lstrip("1") == b.lstrip("1")
+def are_equal(a,b,dtype=''):
+	if dtype == 'str': return a.lstrip('\0') == b.lstrip('\0')
+	if dtype == 'hex': return a.lstrip('0') == b.lstrip('0')
+	if dtype == 'b58': return a.lstrip('1') == b.lstrip('1')
 	else:              return a == b
 
 def print_convert_results(indata,enc,dec,dtype):
 
-	error = False if are_equal(indata,dec,dtype) else True
+	error = (True,False)[are_equal(indata,dec,dtype)]
 
 	if error or opt.verbose:
-		Msg("Input:         %s" % repr(indata))
-		Msg("Encoded data:  %s" % repr(enc))
-		Msg("Recoded data:  %s" % repr(dec))
+		Msg('Input:         %s' % repr(indata))
+		Msg('Encoded data:  %s' % repr(enc))
+		Msg('Recoded data:  %s' % repr(dec))
 	else: Msg(enc)
 
 	if error:
@@ -254,7 +253,7 @@ def hexdump(infile, cols=8, line_nums=True):
 				cols=cols,line_nums=line_nums))
 
 def unhexdump(infile):
-	if sys.platform[:3] == "win":
+	if sys.platform[:3] == 'win':
 		import msvcrt
 		msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
 	sys.stdout.write(decode_pretty_hexdump(
@@ -263,31 +262,31 @@ def unhexdump(infile):
 def strtob58(s):
 	enc = bitcoin.b58encode(s)
 	dec = bitcoin.b58decode(enc)
-	print_convert_results(s,enc,dec,"str")
+	print_convert_results(s,enc,dec,'str')
 
 def hextob58(s,f_enc=bitcoin.b58encode, f_dec=bitcoin.b58decode):
 	enc = f_enc(ba.unhexlify(s))
 	dec = ba.hexlify(f_dec(enc))
-	print_convert_results(s,enc,dec,"hex")
+	print_convert_results(s,enc,dec,'hex')
 
 def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
 	tmp = f_enc(s)
 	if tmp == False: sys.exit(1)
 	enc = ba.hexlify(tmp)
 	dec = f_dec(ba.unhexlify(enc))
-	print_convert_results(s,enc,dec,"b58")
+	print_convert_results(s,enc,dec,'b58')
 
 def b58tostr(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode):
 	enc = f_enc(s)
 	if enc == False: sys.exit(1)
 	dec = f_dec(enc)
-	print_convert_results(s,enc,dec,"b58")
+	print_convert_results(s,enc,dec,'b58')
 
 def b58randenc():
 	r = get_random(32)
 	enc = bitcoin.b58encode(r)
 	dec = bitcoin.b58decode(enc)
-	print_convert_results(r,enc,dec,"str")
+	print_convert_results(r,enc,dec,'str')
 
 def randhex(nbytes='32'):
 	Msg(ba.hexlify(get_random(int(nbytes))))
@@ -296,35 +295,35 @@ def randwif(compressed=False):
 	r_hex = ba.hexlify(get_random(32))
 	enc = bitcoin.hextowif(r_hex,compressed)
 	dec = bitcoin.wiftohex(enc,compressed)
-	print_convert_results(r_hex,enc,dec,"hex")
+	print_convert_results(r_hex,enc,dec,'hex')
 
 def randpair(compressed=False):
 	r_hex = ba.hexlify(get_random(32))
 	wif = bitcoin.hextowif(r_hex,compressed)
 	addr = bitcoin.privnum2addr(int(r_hex,16),compressed)
-	Vmsg("Key (hex):  %s" % r_hex)
-	Vmsg_r("Key (WIF):  "); Msg(wif)
-	Vmsg_r("Addr:       "); Msg(addr)
+	Vmsg('Key (hex):  %s' % r_hex)
+	Vmsg_r('Key (WIF):  '); Msg(wif)
+	Vmsg_r('Addr:       '); Msg(addr)
 
 def wif2addr(wif,compressed=False):
 	s_enc = bitcoin.wiftohex(wif,compressed)
 	if s_enc == False:
-		die(1,"Invalid address")
+		die(1,'Invalid address')
 	addr = bitcoin.privnum2addr(int(s_enc,16),compressed)
-	Vmsg_r("Addr: "); Msg(addr)
+	Vmsg_r('Addr: '); Msg(addr)
 
-wordlists = "electrum","tirosh"
-dfl_wordlist = "electrum"
+wordlists = 'electrum','tirosh'
+dfl_wordlist = 'electrum'
 
 from mmgen.seed import Mnemonic
 def do_random_mn(nbytes,wordlist):
 	hexrand = ba.hexlify(get_random(nbytes))
-	Vmsg("Seed: %s" % hexrand)
-	for wlname in (wordlists if wordlist == "all" else [wordlist]):
-		if wordlist == "all":
-			Msg("%s mnemonic:" % (wlname.capitalize()))
+	Vmsg('Seed: %s' % hexrand)
+	for wlname in ([wordlist],wordlists)[wordlist=='all']:
+		if wordlist == 'all':
+			Msg('%s mnemonic:' % (wlname.capitalize()))
 		mn = Mnemonic.hex2mn(hexrand,wordlist=wlname)
-		Msg(" ".join(mn))
+		Msg(' '.join(mn))
 
 def mn_rand128(wordlist=dfl_wordlist): do_random_mn(16,wordlist)
 def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist)
@@ -332,26 +331,26 @@ def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist)
 
 def hex2mn(s,wordlist=dfl_wordlist):
 	mn = Mnemonic.hex2mn(s,wordlist)
-	Msg(" ".join(mn))
+	Msg(' '.join(mn))
 
 def mn2hex(s,wordlist=dfl_wordlist):
 	hexnum = Mnemonic.mn2hex(s.split(),wordlist)
 	Msg(hexnum)
 
 def b32tohex(s):
-	b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
+	b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
 	Msg(Mnemonic.baseNtohex(32,s,b32a))
 
 def hextob32(s):
-	b32a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
-	Msg("".join(Mnemonic.hextobaseN(32,s,b32a)))
+	b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
+	Msg(''.join(Mnemonic.hextobaseN(32,s,b32a)))
 
 def mn_stats(wordlist=dfl_wordlist):
 	Mnemonic.check_wordlist(wordlist)
 
 def mn_printlist(wordlist=dfl_wordlist):
 	wl = Mnemonic.get_wordlist(wordlist)
-	Msg("\n".join(wl))
+	Msg('\n'.join(wl))
 
 def id8(infile):
 	Msg(make_chksum_8(
@@ -361,13 +360,11 @@ def id6(infile):
 	Msg(make_chksum_6(
 		get_data_from_file(infile,dash=True,silent=True,binary=True)
 	))
-def str2id6(s):  Msg(make_chksum_6("".join(s.split())))
+def str2id6(s):  Msg(make_chksum_6(''.join(s.split())))
 
 # List MMGen addresses and their balances:
 def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False):
 
-	from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr,is_mmgen_seed_id
-
 	usr_addr_list = []
 	if addrs:
 		sid,idxs = split2(addrs,':')
@@ -387,36 +384,37 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 	from decimal import Decimal
 	total = Decimal('0')
 	for d in c.listunspent(0):
-		mmaddr,comment = split2(d.account)
+		mmaddr,comment = split2(d['account'])
 		if usr_addr_list and (mmaddr not in usr_addr_list): continue
-		if is_mmgen_addr(mmaddr) and d.confirmations >= minconf:
+		if is_mmgen_addr(mmaddr) and d['confirmations'] >= minconf:
 			key = mmaddr.replace(':','_')
 			if key in addrs:
-				if addrs[key][2] != d.address:
+				if addrs[key][2] != d['address']:
 					die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
-							(d.address, addrs[key][2])))
+							(d['address'], addrs[key][2])))
 			else:
-				addrs[key] = [0,comment,d.address]
-			addrs[key][0] += d.amount
-			total += d.amount
+				addrs[key] = [0,comment,d['address']]
+			addrs[key][0] += d['amount']
+			total += d['amount']
 
 	# We use listaccounts only for empty addresses, as it shows false positive balances
 	if showempty:
-		accts = c.listaccounts(minconf=0,includeWatchonly=True,as_dict=True)
-		for a in accts:
-			mmaddr,comment = split2(a)
+		accts = c.listaccounts(0,True) # minconf,watchonly
+		save_a = []
+		for acct in accts:
+			mmaddr,comment = split2(acct)
 			if usr_addr_list and (mmaddr not in usr_addr_list): continue
 			if is_mmgen_addr(mmaddr):
 				key = mmaddr.replace(':','_')
 				if key not in addrs:
-					if showbtcaddrs:
-						tmp = c.getaddressesbyaccount(a)
-						if len(tmp) != 1:
-							die(2,"Account '%s' has more or less than one BTC address!" % a)
-						baddr = tmp[0]
-					else:
-						baddr = ''
-					addrs[key] = [0,comment,baddr]
+					if showbtcaddrs: save_a.append([acct])
+					addrs[key] = [0,comment,'']
+
+		for acct,addr in zip(save_a,c.getaddressesbyaccount(save_a,batch=True)):
+			if len(addr) != 1:
+				die(2,"Account '%s' has more or less than one BTC address!" % addr)
+			key = split2(acct[0])[0].replace(':','_')
+			addrs[key][2] = addr[0]
 
 	if not addrs:
 		die(1,('No addresses with balances!','No tracked addresses!')[showempty])
@@ -424,7 +422,7 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 	fs = '%-{}s %-{}s %-{}s %s'.format(
 		max(len(k) for k in addrs),
 		(0,36)[showbtcaddrs],
-		max(len(addrs[k][1]) for k in addrs) + 1
+		max(max(len(addrs[k][1]) for k in addrs) + 1,8) # pad 8 if no comments
 	)
 
 	def s_mmgen(key):
@@ -444,39 +442,38 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
 
 
 def getbalance(minconf=1):
-	from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
 
 	accts = {}
 	for d in connect_to_bitcoind().listunspent(0):
-		ma = split2(d.account)[0]
-		keys = ["TOTAL"]
-		if d.spendable: keys += ["SPENDABLE"]
-		if is_mmgen_addr(ma): keys += [ma.split(":")[0]]
-		confs = d.confirmations
-		i = 2 if confs >= minconf else 1
+		ma = split2(d['account'])[0]
+		keys = ['TOTAL']
+		if d['spendable']: keys += ['SPENDABLE']
+		if is_mmgen_addr(ma): keys += [ma.split(':')[0]]
+		confs = d['confirmations']
+		i = (1,2)[confs >= minconf]
 
 		for key in keys:
 			if key not in accts: accts[key] = [0,0,0]
-			for j in ([0] if confs == 0 else []) + [i]:
-				accts[key][j] += d.amount
+			for j in ([],[0])[confs==0] + [i]:
+				accts[key][j] += d['amount']
 
-	fs = "{:12}  {:<%s} {:<%s} {:<}" % (16,16)
-	mc,lbl = str(minconf),"confirms"
-	Msg(fs.format("Wallet","Unconfirmed","<%s %s"%(mc,lbl),">=%s %s"%(mc,lbl)))
+	fs = '{:12}  {:<%s} {:<%s} {:<}' % (16,16)
+	mc,lbl = str(minconf),'confirms'
+	Msg(fs.format('Wallet','Unconfirmed','<%s %s'%(mc,lbl),'>=%s %s'%(mc,lbl)))
 	for key in sorted(accts.keys()):
-		Msg(fs.format(key+":", *[str(trim_exponent(a))+" BTC"
+		Msg(fs.format(key+':', *[str(trim_exponent(a))+' BTC'
 				for a in accts[key]]))
 
 def txview(infile,pager=False,terse=False):
 	c = connect_to_bitcoind()
-	tx_data = get_lines_from_file(infile,"transaction data")
+	tx_data = get_lines_from_file(infile,'transaction data')
 
 	metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
 	view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager,pause=False,terse=terse)
 
 def add_label(mmaddr,label,remove=False):
 	if not is_mmgen_addr(mmaddr):
-		die(1,"{a}: not a valid {pnm} address".format(pnm=pnm,a=mmaddr))
+		die(1,'{a}: not a valid {pnm} address'.format(pnm=pnm,a=mmaddr))
 	check_addr_label(label)  # Exits on failure
 
 	c = connect_to_bitcoind()
@@ -485,28 +482,28 @@ def add_label(mmaddr,label,remove=False):
 	btcaddr = AddrInfoList(bitcoind_connection=c).mmaddr2btcaddr(mmaddr)
 
 	if not btcaddr:
-		die(1,"{pnm} address {a} not found in tracking wallet".format(
+		die(1,'{pnm} address {a} not found in tracking wallet'.format(
 				pnm=pnm,a=mmaddr))
 
 	try:
-		l = " " + label if label else ""
-		c.importaddress(btcaddr,mmaddr+l,rescan=False)
+		l = ' ' + label if label else ''
+		c.importaddress(btcaddr,mmaddr+l,False) # addr,label,rescan,p2sh
 	except:
-		die(1,"Unable to add label")
+		die(1,'Unable to add label')
 
-	s = "{pnm} address {a} in tracking wallet".format(a=mmaddr,pnm=pnm)
-	if remove: msg("Removed label from {}".format(s))
+	s = '{pnm} address {a} in tracking wallet'.format(a=mmaddr,pnm=pnm)
+	if remove: msg('Removed label from {}'.format(s))
 	else:      msg("Added label '{}' for {}".format(label,s))
 
-def remove_label(mmaddr): add_label(mmaddr,"",remove=True)
+def remove_label(mmaddr): add_label(mmaddr,'',remove=True)
 
 def addrfile_chksum(infile):
 	from mmgen.addr import AddrInfo
-	AddrInfo(infile)
+	AddrInfo(infile,caller='tool')
 
 def keyaddrfile_chksum(infile):
 	from mmgen.addr import AddrInfo
-	AddrInfo(infile,has_keys=True)
+	AddrInfo(infile,has_keys=True,caller='tool')
 
 def hexreverse(hex_str):
 	Msg(ba.hexlify(decode_pretty_hexdump(hex_str)[::-1]))
@@ -543,36 +540,36 @@ def hex2wif(hexpriv,compressed=False):
 	Msg(bitcoin.hextowif(hexpriv,compressed))
 
 
-def encrypt(infile,outfile="",hash_preset=""):
-	data = get_data_from_file(infile,"data for encryption",binary=True)
-	enc_d = mmgen_encrypt(data,"user data",hash_preset)
+def encrypt(infile,outfile='',hash_preset=''):
+	data = get_data_from_file(infile,'data for encryption',binary=True)
+	enc_d = mmgen_encrypt(data,'user data',hash_preset)
 	if not outfile:
-		outfile = "%s.%s" % (os.path.basename(infile),g.mmenc_ext)
+		outfile = '%s.%s' % (os.path.basename(infile),g.mmenc_ext)
 
-	write_data_to_file(outfile,enc_d,"encrypted data",binary=True)
+	write_data_to_file(outfile,enc_d,'encrypted data',binary=True)
 
 
-def decrypt(infile,outfile="",hash_preset=""):
-	enc_d = get_data_from_file(infile,"encrypted data",binary=True)
+def decrypt(infile,outfile='',hash_preset=''):
+	enc_d = get_data_from_file(infile,'encrypted data',binary=True)
 	while True:
-		dec_d = mmgen_decrypt(enc_d,"user data",hash_preset)
+		dec_d = mmgen_decrypt(enc_d,'user data',hash_preset)
 		if dec_d: break
-		msg("Trying again...")
+		msg('Trying again...')
 
 	if not outfile:
 		o = os.path.basename(infile)
 		outfile = remove_extension(o,g.mmenc_ext)
-		if outfile == o: outfile += ".dec"
+		if outfile == o: outfile += '.dec'
 
-	write_data_to_file(outfile,dec_d,"decrypted data",binary=True)
+	write_data_to_file(outfile,dec_d,'decrypted data',binary=True)
 
 
 def find_incog_data(filename,iv_id,keep_searching=False):
 	ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
-	n,carry = 0," "*ivsize
+	n,carry = 0,' '*ivsize
 	f = os.open(filename,os.O_RDONLY)
 	for ch in iv_id:
-		if ch not in "0123456789ABCDEF":
+		if ch not in '0123456789ABCDEF':
 			die(2,"'%s': invalid Incog ID" % iv_id)
 	while True:
 		d = os.read(f,bsize)
@@ -581,14 +578,14 @@ def find_incog_data(filename,iv_id,keep_searching=False):
 		for i in range(bsize):
 			if sha256(d[i:i+ivsize]).hexdigest()[:8].upper() == iv_id:
 				if n+i < ivsize: continue
-				msg("\rIncog data for ID %s found at offset %s" %
+				msg('\rIncog data for ID %s found at offset %s' %
 					(iv_id,n+i-ivsize))
-				if not keep_searching: sys.exit(0)
+				if not keep_searching: sys.exit()
 		carry = d[len(d)-ivsize:]
 		n += bsize
-		if not n % mod: msg_r("\rSearched: %s bytes" % n)
+		if not n % mod: msg_r('\rSearched: %s bytes' % n)
 
-	msg("")
+	msg('')
 	os.close(f)
 
 
@@ -601,7 +598,7 @@ def rand2file(outfile, nbytes, threads=4, silent=False):
 	bsize = 2**20
 	roll = bsize * 4
 	if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
-	f = open(outfile,"wb")
+	f = open(outfile,'wb')
 
 	from Crypto.Cipher import AES
 	from Crypto.Util import Counter
@@ -641,10 +638,10 @@ def rand2file(outfile, nbytes, threads=4, silent=False):
 		rbytes -= bsize
 		i += 1
 		if not (bsize*i) % roll:
-			msg_r("\rRead: %s bytes" % (bsize*i))
+			msg_r('\rRead: %s bytes' % (bsize*i))
 
 	if not silent:
-		msg("\rRead: %s bytes" % nbytes)
+		msg('\rRead: %s bytes' % nbytes)
 		qmsg("\r%s bytes of random data written to file '%s'" % (nbytes,outfile))
 	q1.join()
 	q2.join()

+ 99 - 102
mmgen/tx.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,13 +21,12 @@ tx.py:  Bitcoin transaction routines
 """
 
 import sys, os
+from stat import *
 from binascii import unhexlify
 from decimal import Decimal
 from collections import OrderedDict
 
-import mmgen.globalvars as g
-import mmgen.opt as opt
-from mmgen.util import *
+from mmgen.common import *
 from mmgen.term import do_pager
 
 def trim_exponent(n):
@@ -43,30 +42,30 @@ def normalize_btc_amt(amt):
 	try:
 		ret = Decimal(amt)
 	except:
-		msg("%s: Invalid amount" % amt)
+		msg('%s: Invalid amount' % amt)
 		return False
 
-	dmsg("Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple())))
+	dmsg('Decimal(amt): %s\nAs tuple: %s' % (amt,repr(ret.as_tuple())))
 
 	if ret.as_tuple()[-1] < -8:
-		msg("%s: Too many decimal places in amount" % amt)
+		msg('%s: Too many decimal places in amount' % amt)
 		return False
 
 	if ret == 0:
-		msg("Requested zero BTC amount")
+		msg('Requested zero BTC amount')
 		return False
 
 	return trim_exponent(ret)
 
 def parse_mmgen_label(s,check_label_len=False):
 	l = split2(s)
-	if not is_mmgen_addr(l[0]): return "",s
+	if not is_mmgen_addr(l[0]): return '',s
 	if check_label_len: check_addr_label(l[1])
 	return tuple(l)
 
 def is_mmgen_seed_id(s):
 	import re
-	return re.match(r"^[0123456789ABCDEF]{8}$",s) is not None
+	return re.match(r'^[0123456789ABCDEF]{8}$',s) is not None
 
 def is_mmgen_idx(s):
 	try: int(s)
@@ -74,7 +73,7 @@ def is_mmgen_idx(s):
 	return len(s) <= g.mmgen_idx_max_digits
 
 def is_mmgen_addr(s):
-	seed_id,idx = split2(s,":")
+	seed_id,idx = split2(s,':')
 	return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
 
 def is_btc_addr(s):
@@ -86,13 +85,13 @@ def is_b58_str(s):
 	return set(list(s)) <= set(b58a)
 
 def is_wif(s):
-	if s == "": return False
+	if s == '': return False
 	compressed = not s[0] == '5'
 	from mmgen.bitcoin import wiftohex
 	return wiftohex(s,compressed) is not False
 
 def wiftoaddr(s):
-	if s == "": return False
+	if s == '': return False
 	compressed = not s[0] == '5'
 	from mmgen.bitcoin import wiftohex,privnum2addr
 	hex_key = wiftohex(s,compressed)
@@ -102,13 +101,13 @@ def wiftoaddr(s):
 
 def is_valid_tx_comment(s):
 
-	try: s = s.decode("utf8")
+	try: s = s.decode('utf8')
 	except:
-		msg("Invalid transaction comment (not UTF-8)")
+		msg('Invalid transaction comment (not UTF-8)')
 		return False
 
 	if len(s) > g.max_tx_comment_len:
-		msg("Invalid transaction comment (longer than %s characters)" %
+		msg('Invalid transaction comment (longer than %s characters)' %
 				g.max_tx_comment_len)
 		return False
 
@@ -125,35 +124,38 @@ def check_addr_label(label):
 	for ch in label:
 		if ch not in g.addr_label_symbols:
 			msg("""
-"%s": illegal character in label "%s".
+'%s': illegal character in label '%s'.
 Only ASCII printable characters are permitted.
 """.strip() % (ch,label))
 			sys.exit(3)
 
 def prompt_and_view_tx_data(c,prompt,inputs_data,tx_hex,adata,comment,metadata):
 
-	prompt += " (y)es, (N)o, pager (v)iew, (t)erse view"
+	prompt += ' (y)es, (N)o, pager (v)iew, (t)erse view'
 
-	reply = prompt_and_get_char(prompt,"YyNnVvTt",enter_ok=True)
+	reply = prompt_and_get_char(prompt,'YyNnVvTt',enter_ok=True)
 
-	if reply and reply in "YyVvTt":
+	if reply and reply in 'YyVvTt':
 		view_tx_data(c,inputs_data,tx_hex,adata,comment,metadata,
-				pager=reply in "Vv",terse=reply in "Tt")
+				pager=reply in 'Vv',terse=reply in 'Tt')
 
 
 def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause=True,terse=False):
 
 	td = c.decoderawtransaction(tx_hex)
 
-	fs = "Transaction {} - {} BTC - {} GMT\n" if terse else \
-	"TRANSACTION DATA\n\nHeader: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n"
+	fs = (
+		'TRANSACTION DATA\n\nHeader: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n',
+		'Transaction {} - {} BTC - {} GMT\n'
+	)[bool(terse)]
+
 	out = fs.format(*metadata)
 
-	enl = "" if terse else "\n"
-	if comment: out += "Comment: %s\n%s" % (comment,enl)
-	out += "Inputs:\n" + enl
+	enl = ('\n','')[bool(terse)]
+	if comment: out += 'Comment: %s\n%s' % (comment,enl)
+	out += 'Inputs:\n' + enl
 
-	nonmm_str = "non-{pnm} address".format(pnm=g.proj_name)
+	nonmm_str = 'non-{pnm} address'.format(pnm=g.proj_name)
 
 	total_in = 0
 	for n,i in enumerate(td['vin']):
@@ -162,93 +164,97 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause
 				days = int(j['confirmations'] * g.mins_per_block / (60*24))
 				total_in += j['amount']
 				if not j['mmid']: j['mmid'] = nonmm_str
-				mmid_fmt = " ({:>{l}})".format(j['mmid'],l=34-len(j['address']))
+				mmid_fmt = ' ({:>{l}})'.format(j['mmid'],l=34-len(j['address']))
 				if terse:
-					out += "  %s: %-54s %s BTC" % (n+1,j['address'] + mmid_fmt,
+					out += '  %s: %-54s %s BTC' % (n+1,j['address'] + mmid_fmt,
 							trim_exponent(j['amount']))
 				else:
 					for d in (
-	(n+1, "tx,vout:",       "%s,%s" % (i['txid'], i['vout'])),
-	("",  "address:",       j['address'] + mmid_fmt),
-	("",  "comment:",       j['comment']),
-	("",  "amount:",        "%s BTC" % trim_exponent(j['amount'])),
-	("",  "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
+	(n+1, 'tx,vout:',       '%s,%s' % (i['txid'], i['vout'])),
+	('',  'address:',       j['address'] + mmid_fmt),
+	('',  'comment:',       j['comment']),
+	('',  'amount:',        '%s BTC' % trim_exponent(j['amount'])),
+	('',  'confirmations:', '%s (around %s days)' % (j['confirmations'], days))
 					):
-						if d[2]: out += ("%3s %-8s %s\n" % d)
-				out += "\n"
+						if d[2]: out += ('%3s %-8s %s\n' % d)
+				out += '\n'
 
 				break
 	total_out = 0
-	out += "Outputs:\n" + enl
+	out += 'Outputs:\n' + enl
 	for n,i in enumerate(td['vout']):
 		btcaddr = i['scriptPubKey']['addresses'][0]
-		mmid,comment=b2m_map[btcaddr] if btcaddr in b2m_map else (nonmm_str,"")
-		mmid_fmt = " ({:>{l}})".format(mmid,l=34-len(j['address']))
+		mmid,comment=b2m_map[btcaddr] if btcaddr in b2m_map else (nonmm_str,'')
+		mmid_fmt = ' ({:>{l}})'.format(mmid,l=34-len(j['address']))
 		total_out += i['value']
 		if terse:
-			out += "  %s: %-54s %s BTC" % (n+1,btcaddr + mmid_fmt,
+			out += '  %s: %-54s %s BTC' % (n+1,btcaddr + mmid_fmt,
 					trim_exponent(i['value']))
 		else:
 			for d in (
-					(n+1, "address:",  btcaddr + mmid_fmt),
-					("",  "comment:",  comment),
-					("",  "amount:",   trim_exponent(i['value']))
+					(n+1, 'address:',  btcaddr + mmid_fmt),
+					('',  'comment:',  comment),
+					('',  'amount:',   trim_exponent(i['value']))
 				):
-				if d[2]: out += ("%3s %-8s %s\n" % d)
-		out += "\n"
+				if d[2]: out += ('%3s %-8s %s\n' % d)
+		out += '\n'
+
+	fs = (
+		'Total input:  %s BTC\nTotal output: %s BTC\nTX fee:       %s BTC\n',
+		'In %s BTC - Out %s BTC - Fee %s BTC\n'
+	)[bool(terse)]
 
-	fs = "In %s BTC - Out %s BTC - Fee %s BTC\n" if terse else \
-		"Total input:  %s BTC\nTotal output: %s BTC\nTX fee:       %s BTC\n"
 	out += fs % (
 		trim_exponent(total_in),
 		trim_exponent(total_out),
 		trim_exponent(total_in-total_out)
 	)
 
-	o = out.encode("utf8")
+	o = out.encode('utf8')
 	if pager: do_pager(o)
 	else:
 		sys.stdout.write(o)
+		from mmgen.term import get_char
 		if pause:
-			get_char("Press any key to continue: ")
-			msg("")
+			get_char('Press any key to continue: ')
+			msg('')
 
 
 def parse_tx_file(tx_data,infile):
 
-	err_str,err_fmt = "","Invalid %s in transaction file"
+	err_str,err_fmt = '','Invalid %s in transaction file'
 
 	if len(tx_data) == 5:
 		metadata,tx_hex,inputs_data,outputs_data,comment = tx_data
 	elif len(tx_data) == 4:
 		metadata,tx_hex,inputs_data,outputs_data = tx_data
-		comment = ""
+		comment = ''
 	else:
-		err_str = "number of lines"
+		err_str = 'number of lines'
 
 	if not err_str:
 		if len(metadata.split()) != 3:
-			err_str = "metadata"
+			err_str = 'metadata'
 		else:
 			try: unhexlify(tx_hex)
-			except: err_str = "hex data"
+			except: err_str = 'hex data'
 			else:
 				try: inputs_data = eval(inputs_data)
-				except: err_str = "inputs data"
+				except: err_str = 'inputs data'
 				else:
 					try: outputs_data = eval(outputs_data)
-					except: err_str = "mmgen-to-btc address map data"
+					except: err_str = 'mmgen-to-btc address map data'
 					else:
 						if comment:
 							from mmgen.bitcoin import b58decode
 							comment = b58decode(comment)
 							if comment == False:
-								err_str = "encoded comment (not base58)"
+								err_str = 'encoded comment (not base58)'
 							else:
 								if is_valid_tx_comment(comment):
-									comment = comment.decode("utf8")
+									comment = comment.decode('utf8')
 								else:
-									err_str = "comment"
+									err_str = 'comment'
 
 	if err_str:
 		msg(err_fmt % err_str)
@@ -260,84 +266,75 @@ def parse_tx_file(tx_data,infile):
 def wiftoaddr_keyconv(wif):
 	if wif[0] == '5':
 		from subprocess import check_output
-		return check_output(["keyconv", wif]).split()[1]
+		return check_output(['keyconv', wif]).split()[1]
 	else:
 		return wiftoaddr(wif)
 
 def get_wif2addr_f():
 	if opt.no_keyconv: return wiftoaddr
 	from mmgen.addr import test_for_keyconv
-	return wiftoaddr_keyconv if test_for_keyconv() else wiftoaddr
+	return (wiftoaddr,wiftoaddr_keyconv)[bool(test_for_keyconv())]
 
 
 def get_tx_comment_from_file(infile):
-	s = get_data_from_file(infile,"transaction comment")
+	s = get_data_from_file(infile,'transaction comment')
 	if is_valid_tx_comment(s):
-		return s.decode("utf8").strip()
+		return s.decode('utf8').strip()
 	else:
 		sys.exit(2)
 
-def get_tx_comment_from_user(comment=""):
+def get_tx_comment_from_user(comment=''):
 	try:
 		while True:
-			s = my_raw_input("Comment: ",insert_txt=comment.encode("utf8"))
-			if s == "": return False
+			s = my_raw_input('Comment: ',insert_txt=comment.encode('utf8'))
+			if s == '': return False
 			if is_valid_tx_comment(s):
-				return s.decode("utf8")
+				return s.decode('utf8')
 	except KeyboardInterrupt:
-		msg("User interrupt")
+		msg('User interrupt')
 		return False
 
 def make_tx_data(metadata_fmt, tx_hex, inputs_data, b2m_map, comment):
 	from mmgen.bitcoin import b58encode
-	s = (b58encode(comment.encode("utf8")),) if comment else ()
+	s = (b58encode(comment.encode('utf8')),) if comment else ()
 	lines = (metadata_fmt, tx_hex, repr(inputs_data), repr(b2m_map)) + s
-	return "\n".join(lines)+"\n"
+	return '\n'.join(lines)+'\n'
 
-def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=""):
-	seed_id,idx = mmaddr.split(":")
+def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=''):
+	seed_id,idx = mmaddr.split(':')
 	if seed_id in addr_data:
 		if idx in addr_data[seed_id]:
-			vmsg("%s -> %s%s" % (mmaddr,addr_data[seed_id][idx][0],
-				" (from "+source+")" if source else ""))
+			vmsg('%s -> %s%s' % (mmaddr,addr_data[seed_id][idx][0],
+				' (from %s)' % source if source else ''))
 			return addr_data[seed_id][idx]
 
-	return "",""
+	return '',''
 
 def get_bitcoind_cfg_options(cfg_keys):
 
-	if "HOME" in os.environ:       # Linux
-		homedir,datadir = os.environ["HOME"],".bitcoin"
-	elif "HOMEPATH" in os.environ: # Windows:
-		homedir,data_dir = os.environ["HOMEPATH"],r"Application Data\Bitcoin"
-	else:
-		msg("Neither $HOME nor %HOMEPATH% are set")
-		msg("Don't know where to look for 'bitcoin.conf'")
-		sys.exit(3)
-
-	cfg_file = os.path.join(homedir, datadir, "bitcoin.conf")
+	cfg_file = os.path.join(get_homedir(), get_datadir(), 'bitcoin.conf')
 
-	cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
+	cfg = dict([(k,v) for k,v in [split2(line.translate(None,'\t '),'=')
 			for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
 
-	for k in set(cfg_keys) - set(cfg.keys()):
-		msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
-		sys.exit(2)
-
+	for k in set(cfg_keys) - set(cfg.keys()): cfg[k] = ''
 	return cfg
 
-def connect_to_bitcoind():
+def get_bitcoind_auth_cookie():
 
-	host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
-	cfg = get_bitcoind_cfg_options((user,passwd))
+	f = os.path.join(get_homedir(), get_datadir(), '.cookie')
 
-	import mmgen.rpc.connection
-	f = mmgen.rpc.connection.BitcoinConnection
+	if file_is_readable(f):
+		return get_lines_from_file(f)[0]
+	else:
+		return ''
 
-	try:
-		c = f(cfg[user],cfg[passwd],host,port)
-	except:
-		msg("Unable to establish RPC connection with bitcoind")
-		sys.exit(2)
+def connect_to_bitcoind():
+
+	host,port,user,passwd = 'localhost',8332,'rpcuser','rpcpassword'
+	cfg = get_bitcoind_cfg_options((user,passwd))
+	auth_cookie = get_bitcoind_auth_cookie()
 
-	return c
+	import mmgen.rpc
+	return mmgen.rpc.BitcoinRPCConnection(
+				host,port,cfg[user],cfg[passwd],auth_cookie=auth_cookie)

+ 189 - 178
mmgen/util.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -30,7 +30,7 @@ import mmgen.globalvars as g
 pnm = g.proj_name
 
 _red,_grn,_yel,_cya,_reset,_grnbg = \
-	["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0","30;102"]
+	['\033[%sm' % c for c in '31;1','32;1','33;1','36;1','0','30;102']
 
 def red(s):     return _red+s+_reset
 def green(s):   return _grn+s+_reset
@@ -40,10 +40,10 @@ def cyan(s):    return _cya+s+_reset
 def nocolor(s): return s
 
 def start_mscolor():
-	if sys.platform[:3] == "win":
+	if sys.platform[:3] == 'win':
 		global red,green,yellow,cyan,nocolor
 		import os
-		if "MMGEN_NOMSCOLOR" in os.environ:
+		if 'MMGEN_NOMSCOLOR' in os.environ:
 			red = green = yellow = cyan = grnbg = nocolor
 		else:
 			try:
@@ -52,47 +52,47 @@ def start_mscolor():
 			except:
 				red = green = yellow = cyan = grnbg = nocolor
 
-def msg(s):    sys.stderr.write(s+"\n")
+def msg(s):    sys.stderr.write(s+'\n')
 def msg_r(s):  sys.stderr.write(s)
-def Msg(s):    sys.stdout.write(s + "\n")
+def Msg(s):    sys.stdout.write(s + '\n')
 def Msg_r(s):  sys.stdout.write(s)
-def msgred(s): sys.stderr.write(red(s+"\n"))
+def msgred(s): sys.stderr.write(red(s+'\n'))
 def mmsg(*args):
 	for d in args:
-		sys.stdout.write(repr(d)+"\n")
+		sys.stdout.write(repr(d)+'\n')
 def mdie(*args):
 	for d in args:
-		sys.stdout.write(repr(d)+"\n")
+		sys.stdout.write(repr(d)+'\n')
 	sys.exit()
 
 def die(ev,s):
-	sys.stderr.write(s+"\n"); sys.exit(ev)
+	sys.stderr.write(s+'\n'); sys.exit(ev)
 def Die(ev,s):
-	sys.stdout.write(s+"\n"); sys.exit(ev)
+	sys.stdout.write(s+'\n'); sys.exit(ev)
 
 def is_mmgen_wallet_label(s):
 	if len(s) > g.max_wallet_label_len:
-		msg("ERROR: wallet label length (%s chars) > maximum allowed (%s chars)" % (len(s),g.max_wallet_label_len))
+		msg('ERROR: wallet label length (%s chars) > maximum allowed (%s chars)' % (len(s),g.max_wallet_label_len))
 		return False
 
-	try: s = s.decode("utf8")
+	try: s = s.decode('utf8')
 	except: pass
 
 	for ch in s:
 		if ch not in g.wallet_label_symbols:
-			msg("ERROR: wallet label contains illegal symbol (%s)" % ch)
+			msg('ERROR: wallet label contains illegal symbol (%s)' % ch)
 			return False
 	return True
 
-# From "man dd":
+# 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.
 
 def parse_nbytes(nbytes):
 	import re
 	m = re.match(r'([0123456789]+)(.*)',nbytes)
-	smap = ("c",1),("w",2),("b",512),("kB",1000),("K",1024),("MB",1000*1000),\
-			("M",1024*1024),("GB",1000*1000*1000),("G",1024*1024*1024)
+	smap = ('c',1),('w',2),('b',512),('kB',1000),('K',1024),('MB',1000*1000),\
+			('M',1024*1024),('GB',1000*1000*1000),('G',1024*1024*1024)
 	if m:
 		if m.group(2):
 			for k,v in smap:
@@ -103,31 +103,30 @@ def parse_nbytes(nbytes):
 		else:
 			return int(nbytes)
 
-	msg("'%s': invalid byte specifier" % nbytes)
-	sys.exit(1)
+	die(1,"'%s': invalid byte specifier" % nbytes)
 
-import opt
+from mmgen.opts import opt
 
 def qmsg(s,alt=False):
 	if opt.quiet:
-		if alt != False: sys.stderr.write(alt + "\n")
-	else: sys.stderr.write(s + "\n")
+		if alt != False: sys.stderr.write(alt + '\n')
+	else: sys.stderr.write(s + '\n')
 def qmsg_r(s,alt=False):
 	if opt.quiet:
 		if alt != False: sys.stderr.write(alt)
 	else: sys.stderr.write(s)
 def vmsg(s):
-	if opt.verbose: sys.stderr.write(s + "\n")
+	if opt.verbose: sys.stderr.write(s + '\n')
 def vmsg_r(s):
 	if opt.verbose: sys.stderr.write(s)
 
 def Vmsg(s):
-	if opt.verbose: sys.stdout.write(s + "\n")
+	if opt.verbose: sys.stdout.write(s + '\n')
 def Vmsg_r(s):
 	if opt.verbose: sys.stdout.write(s)
 
 def dmsg(s):
-	if opt.debug: sys.stdout.write(s + "\n")
+	if opt.debug: sys.stdout.write(s + '\n')
 
 def suf(arg,suf_type):
 	t = type(arg)
@@ -136,31 +135,29 @@ def suf(arg,suf_type):
 	elif t == list or t == tuple or t == set:
 		n = len(arg)
 	else:
-		msg("%s: invalid parameter" % arg)
-		return ""
+		msg('%s: invalid parameter' % arg)
+		return ''
 
-	if suf_type in ("a","es"):
-		return "" if n == 1 else "es"
-	if suf_type in ("k","s"):
-		return "" if n == 1 else "s"
+	if suf_type in ('a','es'): return ('es','')[n == 1]
+	if suf_type in ('k','s'):  return ('s','')[n == 1]
 
 def get_extension(f):
 	a,b = os.path.splitext(f)
-	return ('',b[1:])[int(len(b) > 1)]
+	return ('',b[1:])[len(b) > 1]
 
 def remove_extension(f,e):
 	a,b = os.path.splitext(f)
-	return (f,a)[int(len(b) > 1 and b[1:] == e)]
+	return (f,a)[len(b)>1 and b[1:]==e]
 
 def make_chksum_N(s,nchars,sep=False):
 	if nchars%4 or not (4 <= nchars <= 64): return False
 	s = sha256(sha256(s).digest()).hexdigest().upper()
-	sep = " " if sep else ""
+	sep = ('',' ')[bool(sep)]
 	return sep.join([s[i*4:i*4+4] for i in range(nchars/4)])
 
 def make_chksum_8(s,sep=False):
 	s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
-	return "{} {}".format(s[:4],s[4:]) if sep else s
+	return '{} {}'.format(s[:4],s[4:]) if sep else s
 def make_chksum_6(s): return sha256(s).hexdigest()[:6]
 def is_chksum_6(s): return len(s) == 6 and is_hexstring_lc(s)
 
@@ -168,26 +165,25 @@ def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
 
 def splitN(s,n,sep=None):                      # always return an n-element list
 	ret = s.split(sep,n-1)
-	return ret + ["" for i in range(n-len(ret))]
+	return ret + ['' for i in range(n-len(ret))]
 def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
 def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
 
 def split_into_cols(col_wid,s):
-	return " ".join([s[col_wid*i:col_wid*(i+1)]
+	return ' '.join([s[col_wid*i:col_wid*(i+1)]
 					for i in range(len(s)/col_wid+1)]).rstrip()
 
 def capfirst(s):
-	return s if len(s) == 0 else \
-		(s[0].upper() + (s[1:] if len(s) > 1 else ""))
+	return s if len(s) == 0 else s[0].upper() + s[1:]
 
 def make_timestamp():
 	tv = time.gmtime(time.time())[:6]
-	return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
+	return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*tv)
 def make_timestr():
 	tv = time.gmtime(time.time())[:6]
-	return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
+	return '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(*tv)
 def secs_to_hms(secs):
-	return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
+	return '{:02d}:{:02d}:{:02d}'.format(secs/3600, (secs/60) % 60, secs % 60)
 
 def _is_whatstring(s,chars):
 	return set(list(s)) <= set(chars)
@@ -210,17 +206,17 @@ def is_b58string(s):
 	return _is_whatstring(s,b58a)
 
 def is_utf8(s):
-	try: s.decode("utf8")
+	try: s.decode('utf8')
 	except: return False
 	else: return True
 
 def is_ascii(s):
-	try: s.decode("ascii")
+	try: s.decode('ascii')
 	except: return False
 	else: return True
 
 def match_ext(addr,ext):
-	return addr.split(".")[-1] == ext
+	return addr.split('.')[-1] == ext
 
 def file_exists(f):
 	try:
@@ -229,106 +225,120 @@ def file_exists(f):
 	except:
 		return False
 
+def file_is_readable(f):
+	from stat import S_IREAD
+	try:
+		assert os.stat(f).st_mode & S_IREAD
+	except:
+		return False
+	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(",")
+	l,p = opt.from_brain.split(',')
 	return(int(l),p)
 
 def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
-	r = 1 if len(data) % gw else 0
-	return "".join(
+	r = (0,1)[bool(len(data) % gw)]
+	return ''.join(
 		[
-			("" if (line_nums == False or i % cols) else "{:06x}: ".format(i*gw)) +
-			hexlify(data[i*gw:i*gw+gw]) +
-			(" " if (i+1) % cols else "\n")
-				for i in range(len(data)/gw + r)
+			('' if (line_nums == False or i % cols) else '{:06x}: '.format(i*gw)) +
+				hexlify(data[i*gw:i*gw+gw]) + ('\n',' ')[bool((i+1) % cols)]
+					for i in range(len(data)/gw + r)
 		]
-	).rstrip() + "\n"
+	).rstrip() + '\n'
 
 def decode_pretty_hexdump(data):
 	from string import hexdigits
 	pat = r'^[%s]+:\s+' % hexdigits
 	lines = [re.sub(pat,'',l) for l in data.splitlines()]
 	try:
-		return unhexlify("".join(("".join(lines).split())))
+		return unhexlify(''.join((''.join(lines).split())))
 	except:
-		msg("Data not in hexdump format")
+		msg('Data not in hexdump format')
 		return False
 
 def get_hash_params(hash_preset):
 	if hash_preset in g.hash_presets:
 		return g.hash_presets[hash_preset] # N,p,r,buflen
 	else: # Shouldn't be here
-		msg("%s: invalid 'hash_preset' value" % hash_preset)
-		sys.exit(3)
+		die(3,"%s: invalid 'hash_preset' value" % hash_preset)
 
-def compare_chksums(chk1, desc1, chk2, desc2, hdr="", die_on_fail=False):
+def compare_chksums(chk1, desc1, chk2, desc2, hdr='', die_on_fail=False):
 
 	if not chk1 == chk2:
 		m = "%s ERROR: %s checksum (%s) doesn't match %s checksum (%s)"\
-				% ((hdr+":\n   " if hdr else "CHECKSUM"),desc2,chk2,desc1,chk1)
+				% ((hdr+':\n   ' if hdr else 'CHECKSUM'),desc2,chk2,desc1,chk1)
 		if die_on_fail:
 			die(3,m)
 		else:
 			vmsg(m)
 			return False
 
-	vmsg("%s checksum OK (%s)" % (capfirst(desc1),chk1))
+	vmsg('%s checksum OK (%s)' % (capfirst(desc1),chk1))
 	return True
 
-def compare_or_die(val1, desc1, val2, desc2, e="Error"):
+def compare_or_die(val1, desc1, val2, desc2, e='Error'):
 	if cmp(val1,val2):
 		die(3,"%s: %s (%s) doesn't match %s (%s)"
 				% (e,desc2,val2,desc1,val1))
-	dmsg("%s OK (%s)" % (capfirst(desc2),val2))
+	dmsg('%s OK (%s)' % (capfirst(desc2),val2))
 	return True
 
 def open_file_or_exit(filename,mode):
 	try:
 		f = open(filename, mode)
 	except:
-		op = ("writing","reading")[int('r' in mode)]
+		op = ('writing','reading')['r' in mode]
 		die(2,"Unable to open file '%s' for %s" % (filename,op))
 	return f
 
 
 def check_file_type_and_access(fname,ftype,blkdev_ok=False):
 
-	import os, stat
-
-	a = ((os.R_OK,"read"),(os.W_OK,"writ"))
-	access,m = a[int(ftype in ("output file","output directory"))]
+	a = ((os.R_OK,'read'),(os.W_OK,'writ'))
+	access,m = a[ftype in ('output file','output directory')]
 
 	ok_types = [
-		(stat.S_ISREG,"regular file"),
-		(stat.S_ISLNK,"symbolic link")
+		(stat.S_ISREG,'regular file'),
+		(stat.S_ISLNK,'symbolic link')
 	]
-	if blkdev_ok: ok_types.append((stat.S_ISBLK,"block device"))
-	if ftype == "output directory": ok_types = [(stat.S_ISDIR, "output directory")]
+	if blkdev_ok: ok_types.append((stat.S_ISBLK,'block device'))
+	if ftype == 'output directory': ok_types = [(stat.S_ISDIR, 'output directory')]
 
 	try: mode = os.stat(fname).st_mode
 	except:
-		msg("Unable to stat requested %s '%s'" % (ftype,fname))
-		sys.exit(1)
+		die(1,"Unable to stat requested %s '%s'" % (ftype,fname))
 
 	for t in ok_types:
 		if t[0](mode): break
 	else:
-		msg("Requested %s '%s' is not a %s" % (ftype,fname,
-				" or ".join([t[1] for t in ok_types])))
-		sys.exit(1)
+		die(1,"Requested %s '%s' is not a %s" % (ftype,fname,
+				' or '.join([t[1] for t in ok_types])))
 
 	if not os.access(fname, access):
-		msg("Requested %s '%s' is not %sable by you" % (ftype,fname,m))
-		sys.exit(1)
+		die(1,"Requested %s '%s' is not %sable by you" % (ftype,fname,m))
 
 	return True
 
 def check_infile(f,blkdev_ok=False):
-	return check_file_type_and_access(f,"input file",blkdev_ok=blkdev_ok)
+	return check_file_type_and_access(f,'input file',blkdev_ok=blkdev_ok)
 def check_outfile(f,blkdev_ok=False):
-	return check_file_type_and_access(f,"output file",blkdev_ok=blkdev_ok)
+	return check_file_type_and_access(f,'output file',blkdev_ok=blkdev_ok)
 def check_outdir(f):
-	return check_file_type_and_access(f,"output directory")
+	return check_file_type_and_access(f,'output directory')
 
 def make_full_path(outdir,outfile):
 	return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
@@ -341,13 +351,13 @@ def _validate_addr_num(n):
 		msg("'%s': invalid %s address index" % (n,g.proj_name))
 		return False
 
-def parse_addr_idxs(arg,sep=","):
+def parse_addr_idxs(arg,sep=','):
 
 	ret = []
 
 	for i in (arg.split(sep)):
 
-		j = i.split("-")
+		j = i.split('-')
 
 		if len(j) == 1:
 			i = _validate_addr_num(i)
@@ -371,48 +381,47 @@ def parse_addr_idxs(arg,sep=","):
 
 def get_new_passphrase(desc,passchg=False):
 
-	w = "{}passphrase for {}".format("new " if passchg else "", desc)
+	w = '{}passphrase for {}'.format(('','new ')[bool(passchg)], desc)
 	if opt.passwd_file:
-		pw = " ".join(get_words_from_file(opt.passwd_file,w))
+		pw = ' '.join(get_words_from_file(opt.passwd_file,w))
 	elif opt.echo_passphrase:
-		pw = " ".join(get_words_from_user("Enter {}: ".format(w)))
+		pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
 	else:
 		for i in range(g.passwd_max_tries):
-			pw = " ".join(get_words_from_user("Enter {}: ".format(w)))
-			pw2 = " ".join(get_words_from_user("Repeat passphrase: "))
-			dmsg("Passphrases: [%s] [%s]" % (pw,pw2))
+			pw = ' '.join(get_words_from_user('Enter {}: '.format(w)))
+			pw2 = ' '.join(get_words_from_user('Repeat passphrase: '))
+			dmsg('Passphrases: [%s] [%s]' % (pw,pw2))
 			if pw == pw2:
-				vmsg("Passphrases match"); break
-			else: msg("Passphrases do not match.  Try again.")
+				vmsg('Passphrases match'); break
+			else: msg('Passphrases do not match.  Try again.')
 		else:
-			msg("User failed to duplicate passphrase in %s attempts" %
+			die(2,'User failed to duplicate passphrase in %s attempts' %
 					g.passwd_max_tries)
-			sys.exit(2)
 
-	if pw == "": qmsg("WARNING: Empty passphrase")
+	if pw == '': qmsg('WARNING: Empty passphrase')
 	return pw
 
 
-def confirm_or_exit(message, question, expect="YES"):
+def confirm_or_exit(message, question, expect='YES'):
 
 	m = message.strip()
 	if m: msg(m)
 
-	a = question+"  " if question[0].isupper() else \
-			"Are you sure you want to %s?\n" % question
+	a = question+'  ' if question[0].isupper() else \
+			'Are you sure you want to %s?\n' % question
 	b = "Type uppercase '%s' to confirm: " % expect
 
 	if my_raw_input(a+b).strip() != expect:
-		die(2,"Exiting at user request")
+		die(2,'Exiting at user request')
 
 
 # New function
 def write_data_to_file(
 		outfile,
 		data,
-		desc="data",
+		desc='data',
 		ask_write=False,
-		ask_write_prompt="",
+		ask_write_prompt='',
 		ask_write_default_yes=True,
 		ask_overwrite=True,
 		ask_tty=True,
@@ -428,30 +437,29 @@ def write_data_to_file(
 		ask_write = True
 
 	if opt.stdout or not sys.stdout.isatty() or outfile in ('','-'):
-		qmsg("Output to STDOUT requested")
+		qmsg('Output to STDOUT requested')
 		if sys.stdout.isatty():
 			if no_tty:
-				die(2,"Printing %s to screen is not allowed" % desc)
+				die(2,'Printing %s to screen is not allowed' % desc)
 			if ask_tty and not opt.quiet:
-				confirm_or_exit("",'output %s to screen' % desc)
+				confirm_or_exit('','output %s to screen' % desc)
 		else:
-			try:    of = os.readlink("/proc/%d/fd/1" % os.getpid()) # Linux
+			try:    of = os.readlink('/proc/%d/fd/1' % os.getpid()) # Linux
 			except: of = None # Windows
 
 			if of:
-				if of[:5] == "pipe:":
+				if of[:5] == 'pipe:':
 					if no_tty:
-						die(2,"Writing %s to pipe is not allowed" % desc)
+						die(2,'Writing %s to pipe is not allowed' % desc)
 					if ask_tty and not opt.quiet:
-						confirm_or_exit("",'output %s to pipe' % desc)
-						msg("")
+						confirm_or_exit('','output %s to pipe' % desc)
+						msg('')
 				of2,pd = os.path.relpath(of),os.path.pardir
-				msg("Redirecting output to file '%s'" %
-						(of if of2[:len(pd)] == pd else of2))
+				msg("Redirecting output to file '%s'" % (of2,of)[of2[:len(pd)] == pd])
 			else:
-				msg("Redirecting output to file")
+				msg('Redirecting output to file')
 
-		if binary and sys.platform[:3] == "win":
+		if binary and sys.platform[:3] == 'win':
 			import msvcrt
 			msvcrt.setmode(sys.stdout.fileno(),os.O_BINARY)
 
@@ -460,19 +468,19 @@ def write_data_to_file(
 		if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
 
 		if ask_write:
-			if not ask_write_prompt: ask_write_prompt = "Save %s?" % desc
+			if not ask_write_prompt: ask_write_prompt = 'Save %s?' % desc
 			if not keypress_confirm(ask_write_prompt,
 						default_yes=ask_write_default_yes):
-				die(1,"%s not saved" % capfirst(desc))
+				die(1,'%s not saved' % capfirst(desc))
 
 		hush = False
 		if file_exists(outfile) and ask_overwrite:
 			q = "File '%s' already exists\nOverwrite?" % outfile
-			confirm_or_exit("",q)
+			confirm_or_exit('',q)
 			msg("Overwriting file '%s'" % outfile)
 			hush = True
 
-		f = open_file_or_exit(outfile,'w'+('','b')[int(binary)])
+		f = open_file_or_exit(outfile,('w','wb')[bool(binary)])
 		try:
 			f.write(data)
 		except:
@@ -484,23 +492,22 @@ def write_data_to_file(
 
 		return True
 
-from mmgen.bitcoin import b58decode_pad,b58encode_pad
 
 def _check_mmseed_format(words):
 
 	valid = False
-	desc = "%s data" % g.seed_ext
+	desc = '%s data' % g.seed_ext
 	try:
 		chklen = len(words[0])
 	except:
 		return False
 
 	if len(words) < 3 or len(words) > 12:
-		msg("Invalid data length (%s) in %s" % (len(words),desc))
+		msg('Invalid data length (%s) in %s' % (len(words),desc))
 	elif not is_hexstring(words[0]):
 		msg("Invalid format of checksum '%s' in %s"%(words[0], desc))
 	elif chklen != 6:
-		msg("Incorrect length of checksum (%s) in %s" % (chklen,desc))
+		msg('Incorrect length of checksum (%s) in %s' % (chklen,desc))
 	else: valid = True
 
 	return valid
@@ -512,31 +519,29 @@ def _check_wallet_format(infile, lines):
 	valid = False
 	chklen = len(lines[0])
 	if len(lines) != 6:
-		vmsg("Invalid number of lines (%s) in %s" % (len(lines),desc))
+		vmsg('Invalid number of lines (%s) in %s' % (len(lines),desc))
 	elif chklen != 6:
-		vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,desc))
+		vmsg('Incorrect length of Master checksum (%s) in %s' % (chklen,desc))
 	elif not is_hexstring(lines[0]):
 		vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], desc))
 	else: valid = True
 
 	if valid == False:
-		msg("Invalid %s" % desc)
-		sys.exit(2)
+		die(2,'Invalid %s' % desc)
 
 
 def _check_chksum_6(chk,val,desc,infile):
 	comp_chk = make_chksum_6(val)
 	if chk != comp_chk:
 		msg("%s checksum incorrect in file '%s'!" % (desc,infile))
-		msg("Checksum: %s. Computed value: %s" % (chk,comp_chk))
-		sys.exit(2)
-	dmsg("%s checksum passed: %s" % (capfirst(desc),chk))
+		die(2,'Checksum: %s. Computed value: %s' % (chk,comp_chk))
+	dmsg('%s checksum passed: %s' % (capfirst(desc),chk))
 
 
 def get_words_from_user(prompt):
 	# split() also strips
 	words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
-	dmsg("Sanitized input: [%s]" % " ".join(words))
+	dmsg('Sanitized input: [%s]' % ' '.join(words))
 	return words
 
 
@@ -547,7 +552,7 @@ def get_words_from_file(infile,desc,silent=False):
 	# split() also strips
 	words = f.read().split()
 	f.close()
-	dmsg("Sanitized input: [%s]" % " ".join(words))
+	dmsg('Sanitized input: [%s]' % ' '.join(words))
 	return words
 
 
@@ -566,8 +571,8 @@ def remove_comments(lines):
 		if i: ret.append(i)
 	return ret
 
-def get_lines_from_file(infile,desc="",trim_comments=False):
-	if desc != "":
+def get_lines_from_file(infile,desc='',trim_comments=False):
+	if desc != '':
 		qmsg("Getting %s from file '%s'" % (desc,infile))
 	f = open_file_or_exit(infile,'r')
 	lines = f.read().splitlines() # DOS-safe
@@ -575,16 +580,16 @@ def get_lines_from_file(infile,desc="",trim_comments=False):
 	return remove_comments(lines) if trim_comments else lines
 
 
-def get_data_from_user(desc="data",silent=False):
-	data = my_raw_input("Enter %s: " % desc, echo=opt.echo_passphrase)
-	dmsg("User input: [%s]" % data)
+def get_data_from_user(desc='data',silent=False):
+	data = my_raw_input('Enter %s: ' % desc, echo=opt.echo_passphrase)
+	dmsg('User input: [%s]' % data)
 	return data
 
-def get_data_from_file(infile,desc="data",dash=False,silent=False,binary=False):
-	if dash and infile == "-": return sys.stdin.read()
+def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False):
+	if dash and infile == '-': return sys.stdin.read()
 	if not silent:
 		qmsg("Getting %s from file '%s'" % (desc,infile))
-	f = open_file_or_exit(infile,'r'+('','b')[int(binary)])
+	f = open_file_or_exit(infile,('r','rb')[bool(binary)])
 	data = f.read()
 	f.close()
 	return data
@@ -593,25 +598,26 @@ def get_data_from_file(infile,desc="data",dash=False,silent=False,binary=False):
 def get_seed_from_seed_data(words):
 
 	if not _check_mmseed_format(words):
-		msg("Invalid %s data" % g.seed_ext)
+		msg('Invalid %s data' % g.seed_ext)
 		return False
 
 	stored_chk = words[0]
-	seed_b58 = "".join(words[1:])
+	seed_b58 = ''.join(words[1:])
 
 	chk = make_chksum_6(seed_b58)
-	vmsg_r("Validating %s checksum..." % g.seed_ext)
+	vmsg_r('Validating %s checksum...' % g.seed_ext)
 
-	if compare_chksums(chk, "seed", stored_chk, "input"):
+	if compare_chksums(chk, 'seed', stored_chk, 'input'):
+		from mmgen.bitcoin import b58decode_pad
 		seed = b58decode_pad(seed_b58)
 		if seed == False:
-			msg("Invalid b58 number: %s" % val)
+			msg('Invalid b58 number: %s' % val)
 			return False
 
-		msg("Valid seed data for Seed ID %s" % make_chksum_8(seed))
+		msg('Valid seed data for Seed ID %s' % make_chksum_8(seed))
 		return seed
 	else:
-		msg("Invalid checksum for {pnm} seed".format(pnm=pnm))
+		msg('Invalid checksum for {pnm} seed'.format(pnm=pnm))
 		return False
 
 
@@ -627,18 +633,18 @@ def pwfile_reuse_warning():
 
 
 def get_mmgen_passphrase(desc,passchg=False):
-	prompt ="Enter {}passphrase for {}: ".format("old " if passchg else "",desc)
+	prompt ='Enter {}passphrase for {}: '.format(('','old ')[bool(passchg)],desc)
 	if opt.passwd_file:
 		pwfile_reuse_warning()
-		return " ".join(get_words_from_file(opt.passwd_file,"passphrase"))
+		return ' '.join(get_words_from_file(opt.passwd_file,'passphrase'))
 	else:
-		return " ".join(get_words_from_user(prompt))
+		return ' '.join(get_words_from_user(prompt))
 
 
 def get_bitcoind_passphrase(prompt):
 	if opt.passwd_file:
 		pwfile_reuse_warning()
-		return get_data_from_file(opt.passwd_file,"passphrase").strip("\r\n")
+		return get_data_from_file(opt.passwd_file,'passphrase').strip('\r\n')
 	else:
 		return my_raw_input(prompt, echo=opt.echo_passphrase)
 
@@ -653,16 +659,13 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action):
 		fsize = os.stat(fname).st_size
 
 	if fsize < offset + dlen:
-		m = "Destination" if action == "write" else "Input"
-		msg(
-	"%s file has length %s, too short to %s %s bytes of data at offset %s"
+		m = ('Input','Destination')[action == 'write']
+		die(1,
+	'%s file has length %s, too short to %s %s bytes of data at offset %s'
 			% (m,fsize,action,dlen,offset))
-		sys.exit(1)
-
 
-from mmgen.term import kb_hold_protect,get_char
 
-def get_hash_preset_from_user(hp=g.hash_preset,desc="data"):
+def get_hash_preset_from_user(hp=g.hash_preset,desc='data'):
 	p = """Enter hash preset for %s,
  or hit ENTER to accept the default value ('%s'): """ % (desc,hp)
 	while True:
@@ -670,13 +673,13 @@ def get_hash_preset_from_user(hp=g.hash_preset,desc="data"):
 		if ret:
 			if ret in g.hash_presets.keys(): return ret
 			else:
-				msg("Invalid input.  Valid choices are %s" %
-						", ".join(sorted(g.hash_presets.keys())))
+				msg('Invalid input.  Valid choices are %s' %
+						', '.join(sorted(g.hash_presets.keys())))
 				continue
 		else: return hp
 
 
-def my_raw_input(prompt,echo=True,insert_txt="",use_readline=True):
+def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
 
 	try: import readline
 	except: use_readline = False # Windows
@@ -686,7 +689,9 @@ def my_raw_input(prompt,echo=True,insert_txt="",use_readline=True):
 		readline.set_startup_hook(st_hook)
 	else:
 		msg_r(prompt)
-		prompt = ""
+		prompt = ''
+
+	from mmgen.term import kb_hold_protect
 
 	kb_hold_protect()
 	if echo:
@@ -701,50 +706,56 @@ def my_raw_input(prompt,echo=True,insert_txt="",use_readline=True):
 
 def keypress_confirm(prompt,default_yes=False,verbose=False):
 
-	q = "(Y/n)" if default_yes else "(y/N)"
+	from mmgen.term import get_char
+
+	q = ('(y/N)','(Y/n)')[bool(default_yes)]
 
 	while True:
-		reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
+		reply = get_char('%s %s: ' % (prompt, q)).strip('\n\r')
 
 		if not reply:
-			if default_yes: msg(""); return True
-			else:           msg(""); return False
-		elif reply in 'yY': msg(""); return True
-		elif reply in 'nN': msg(""); return False
+			if default_yes: msg(''); return True
+			else:           msg(''); return False
+		elif reply in 'yY': msg(''); return True
+		elif reply in 'nN': msg(''); return False
 		else:
-			if verbose: msg("\nInvalid reply")
-			else: msg_r("\r")
+			if verbose: msg('\nInvalid reply')
+			else: msg_r('\r')
 
 
 def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
 
+	from mmgen.term import get_char
+
 	while True:
-		reply = get_char("%s: " % prompt).strip("\n\r")
+		reply = get_char('%s: ' % prompt).strip('\n\r')
 
 		if reply in chars or (enter_ok and not reply):
-			msg("")
+			msg('')
 			return reply
 
-		if verbose: msg("\nInvalid reply")
-		else: msg_r("\r")
+		if verbose: msg('\nInvalid reply')
+		else: msg_r('\r')
 
 
 def do_license_msg(immed=False):
 
-	import mmgen.license as gpl
 	if opt.quiet or g.no_license: return
 
+	import mmgen.license as gpl
+
 	p = "Press 'w' for conditions and warranty info, or 'c' to continue:"
 	msg(gpl.warning)
-	prompt = "%s " % p.strip()
+	prompt = '%s ' % p.strip()
+
+	from mmgen.term import get_char,do_pager
 
 	while True:
-		reply = get_char(prompt, immed_chars="wc" if immed else "")
+		reply = get_char(prompt, immed_chars=('','wc')[bool(immed)])
 		if reply == 'w':
-			from mmgen.term import do_pager
 			do_pager(gpl.conditions)
 		elif reply == 'c':
-			msg(""); break
+			msg(''); break
 		else:
-			msg_r("\r")
-	msg("")
+			msg_r('\r')
+	msg('')

+ 6 - 15
setup.py

@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,19 +20,20 @@ from distutils.core import setup
 
 setup(
 		name         = 'mmgen',
-		description   = 'A complete Bitcoin cold-storage solution for the command line',
-		version      = '0.8.2',
+		description  = 'A complete Bitcoin cold-storage solution for the command line',
+		version      = '0.8.3',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
 		license      = 'GNU GPL v3',
 		platforms    = 'Linux, MS Windows',
-		keywords     = 'Bitcoin, wallet, cold storage, offline storage, open-source, command-line, Python, Bitcoin Core, bitcoind',
+		keywords     = 'Bitcoin, wallet, cold storage, offline storage, open-source, command-line, Python, Bitcoin Core, bitcoind, hd, deterministic, hierarchical',
 		py_modules = [
 			'mmgen.__init__',
 			'mmgen.addr',
 			'mmgen.bitcoin',
 			'mmgen.globalvars',
+			'mmgen.common',
 			'mmgen.crypto',
 			'mmgen.filename',
 			'mmgen.license',
@@ -40,7 +41,7 @@ setup(
 			'mmgen.mn_tirosh',
 			'mmgen.obj',
 			'mmgen.opts',
-			'mmgen.opt',
+			'mmgen.rpc',
 			'mmgen.seed',
 			'mmgen.term',
 			'mmgen.test',
@@ -51,7 +52,6 @@ setup(
 			'mmgen.main',
 			'mmgen.main_addrgen',
 			'mmgen.main_addrimport',
-			'mmgen.main_pywallet',
 			'mmgen.main_tool',
 			'mmgen.main_txcreate',
 			'mmgen.main_txsend',
@@ -61,14 +61,6 @@ setup(
 			'mmgen.share.__init__',
 			'mmgen.share.Opts',
 
-			'mmgen.rpc.__init__',
-			'mmgen.rpc.config',
-			'mmgen.rpc.connection',
-			'mmgen.rpc.data',
-			'mmgen.rpc.exceptions',
-			'mmgen.rpc.proxy',
-			'mmgen.rpc.util',
-
 			'test.__init__',
 			'test.test',
 			'test.tooltest',
@@ -85,7 +77,6 @@ setup(
 			'mmgen-txcreate',
 			'mmgen-txsign',
 			'mmgen-txsend',
-			'mmgen-pywallet',
 			'mmgen-tool',
 		]
 	)

+ 35 - 22
test/gentest.py

@@ -1,8 +1,25 @@
 #!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+test/gentest.py:  Bitcoin key/address generation tests for the MMGen suite
+"""
 
-# Chdir to repo root.
-# Since script is not in repo root, fix sys.path so that modules are
-# imported from repo, not system.
 import sys,os
 pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
@@ -10,9 +27,8 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
 from binascii import hexlify
 
-import mmgen.opt as opt
-import mmgen.globalvars as g
-from mmgen.util import msg,msg_r,mmsg,mdie,red,green,vmsg,start_mscolor
+# Import these _after_ local path's been added to sys.path
+from mmgen.common import *
 from mmgen.bitcoin import hextowif,privnum2addr
 
 start_mscolor()
@@ -20,7 +36,7 @@ start_mscolor()
 rounds = 100
 opts_data = {
 	'desc': "Test addresses generated by {} against output of 'keyconv'".format(g.proj_name),
-	'usage':"[options] [rounds]",
+	'usage':'[options] [rounds]',
 	'options': """
 -h, --help         Print this help message
 -s, --system       Test scripts and modules installed on system rather than
@@ -37,45 +53,42 @@ routines, which use the Python ecdsa library.
 rounds is {} by default.
 """.format(rounds,pnm=g.proj_name)
 }
-cmd_args = opt.opts.init(opts_data,add_opts=["exact_output"])
+cmd_args = opts.init(opts_data,add_opts=['exact_output'])
 
 if len(cmd_args) == 1:
 	try:
 		rounds = int(cmd_args[0])
 		assert rounds > 0
 	except:
-		msg("'rounds' must be a positive integer")
-		sys.exit(1)
+		die(1,"'rounds' must be a positive integer")
 
 elif len(cmd_args) > 1:
-	opt.opts.usage(opts_data)
+	opts.usage(opts_data)
 
 if opt.system: sys.path.pop(0)
 
 from mmgen.addr import test_for_keyconv
 if not test_for_keyconv(silent=True):
-	msg(
-"To run this test, you must install 'keyconv' from the vanitygen package.")
-	sys.exit(1)
+	die(1,"To run this test, you must install 'keyconv' from the vanitygen package.")
 
-msg(green("Comparing {}'s internally generated addresses against output of 'keyconv'").format(g.proj_name))
+m = "Comparing {}'s internally generated addresses against output of 'keyconv'"
+msg(green(m.format(g.proj_name)))
 
 from subprocess import check_output
 for i in range(1,rounds+1):
-	msg_r("\rRound %s/%s " % (i,rounds))
+	msg_r('\rRound %s/%s ' % (i,rounds))
 	sec = hexlify(os.urandom(32))
 	wif = hextowif(sec)
 	a = privnum2addr(int(sec,16))
-	vmsg("\nkey:  %s\naddr: %s\n" % (wif,a))
-	b = check_output(["keyconv", wif]).split()[1]
+	vmsg('\nkey:  %s\naddr: %s\n' % (wif,a))
+	b = check_output(['keyconv', wif]).split()[1]
 	if a != b:
-		msg_r(red("\nERROR: Addresses do not match!"))
-		msg("""
+		msg_r(red('\nERROR: Addresses do not match!'))
+		die(3,"""
   sec key: {}
   WIF key: {}
   {pnm}:   {}
   keyconv: {}
 """.format(sec,wif,a,b,pnm=g.proj_name).rstrip())
-		sys.exit(3)
 
-msg(green("%sOK" % ("" if opt.verbose else "\n")))
+msg(green(('\n','')[bool(opt.verbose)] + 'OK'))

File diff suppressed because it is too large
+ 332 - 319
test/test.py


+ 153 - 104
test/tooltest.py

@@ -1,64 +1,81 @@
 #!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+test/tooltest.py:  Tests for the 'mmgen-tool' utility
+"""
 
-# Chdir to repo root.
-# Since script is not in repo root, fix sys.path so that modules are
-# imported from repo, not system.
 import sys,os
 pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
 sys.path.__setitem__(0,os.path.abspath(os.curdir))
 
-import mmgen.opt as opt
-from mmgen.util import *
-from collections import OrderedDict
+# Import this _after_ local path's been added to sys.path
+from mmgen.common import *
 
 start_mscolor()
 
+from collections import OrderedDict
 cmd_data = OrderedDict([
 	('util', {
-			'desc': "base conversion, hashing and file utilities",
+			'desc': 'base conversion, hashing and file utilities',
 			'cmd_data': OrderedDict([
 				('strtob58',     ()),
-				('b58tostr',     ("strtob58","io")),
+				('b58tostr',     ('strtob58','io')),
 				('hextob58',     ()),
-				('b58tohex',     ("hextob58","io")),
+				('b58tohex',     ('hextob58','io')),
 				('b58randenc',   ()),
 				('hextob32',     ()),
-				('b32tohex',     ("hextob32","io")),
+				('b32tohex',     ('hextob32','io')),
 				('randhex',      ()),
 				('id8',          ()),
 				('id6',          ()),
 				('str2id6',      ()),
-				("sha256x2",     ()),
-				("hexreverse",   ()),
-				("hexlify",      ()),
+				('sha256x2',     ()),
+				('hexreverse',   ()),
+				('hexlify',      ()),
 				('hexdump',      ()),
-				('unhexdump',    ("hexdump","io")),
+				('unhexdump',    ('hexdump','io')),
 				('rand2file',    ()),
 			])
 		}
 	),
 	('bitcoin', {
-			'desc': "Bitcoin address/key commands",
+			'desc': 'Bitcoin address/key commands',
 			'cmd_data': OrderedDict([
 				('randwif',      ()),
 				('randpair',     ()),
-				('wif2addr',     ("randpair","o2")),
-				('wif2hex',      ("randpair","o2")),
-				('privhex2addr', ("wif2hex","o2")), # wif from randpair o2
-				('hex2wif',      ("wif2hex","io2")),
-				('addr2hexaddr', ("randpair","o2")),
-				('hexaddr2addr', ("addr2hexaddr","io2")),
-# ("pubkey2addr",  ['<public key in hex format> [str]']),
-# ("pubkey2hexaddr", ['<public key in hex format> [str]']),
+				('wif2addr',     ('randpair','o2')),
+				('wif2hex',      ('randpair','o2')),
+				('privhex2addr', ('wif2hex','o2')), # wif from randpair o2
+				('hex2wif',      ('wif2hex','io2')),
+				('addr2hexaddr', ('randpair','o2')),
+				('hexaddr2addr', ('addr2hexaddr','io2')),
+# ('pubkey2addr',  ['<public key in hex format> [str]']),
+# ('pubkey2hexaddr', ['<public key in hex format> [str]']),
 			])
 		}
 	),
 	('mnemonic', {
-			'desc': "mnemonic commands",
+			'desc': 'mnemonic commands',
 			'cmd_data': OrderedDict([
 				('hex2mn',       ()),
-				('mn2hex',       ("hex2mn","io3")),
+				('mn2hex',       ('hex2mn','io3')),
 				('mn_rand128',   ()),
 				('mn_rand192',   ()),
 				('mn_rand256',   ()),
@@ -66,19 +83,34 @@ cmd_data = OrderedDict([
 				('mn_printlist', ()),
 			])
 		}
-	)
+	),
+	('rpc', {
+			'desc': 'Bitcoind RPC commands',
+			'cmd_data': OrderedDict([
+#				('keyaddrfile_chksum', ()), # interactive
+				('addrfile_chksum', ()),
+				('getbalance',      ()),
+				('listaddresses',   ()),
+				('txview',          ()),
+			])
+		}
+	),
 ])
 
 cfg = {
-	'name':          "the tool utility",
-	'enc_passwd':    "Ten Satoshis",
-	'tmpdir':        "test/tmp10",
+	'name':          'the tool utility',
+	'enc_passwd':    'Ten Satoshis',
+	'tmpdir':        'test/tmp10',
 	'tmpdir_num':    10,
+	'refdir':        'test/ref',
+	'txfile':        'tx_FFB367[1.234].raw',
+	'addrfile':      '98831F3A[1,31-33,500-501,1010-1011].addrs',
+	'addrfile_chk':  '6FEF 6FB9 7B13 5D91 854A 0BD3',
 }
 
 opts_data = {
 	'desc': "Test suite for the 'mmgen-tool' utility",
-	'usage':"[options] [command]",
+	'usage':'[options] [command]',
 	'options': """
 -h, --help          Print this help message
 -l, --list-cmds     List and describe the tests and commands in the test suite
@@ -92,18 +124,18 @@ If no command is given, the whole suite of tests is run.
 """
 }
 
-cmd_args = opt.opts.init(opts_data,add_opts=["exact_output"])
+cmd_args = opts.init(opts_data,add_opts=['exact_output'])
 
 if opt.system: sys.path.pop(0)
 
 if opt.list_cmds:
-	fs = "  {:<{w}} - {}"
-	Msg("Available commands:")
+	fs = '  {:<{w}} - {}'
+	Msg('Available commands:')
 	w = max([len(i) for i in cmd_data])
 	for cmd in cmd_data:
 		Msg(fs.format(cmd,cmd_data[cmd]['desc'],w=w))
-	Msg("\nAvailable utilities:")
-	Msg(fs.format("clean","Clean the tmp directory",w=w))
+	Msg('\nAvailable utilities:')
+	Msg(fs.format('clean','Clean the tmp directory',w=w))
 	sys.exit()
 
 import binascii
@@ -119,15 +151,13 @@ class MMGenToolTestSuite(object):
 		fns = []
 		if cdata:
 			name,code = cdata
-			io,count = code,1
-			if code[-1] in "0123456789":
-				io,count = code[:-1],int(code[-1])
+			io,count = (code[:-1],int(code[-1])) if code[-1] in '0123456789' else (code,1)
 
 			for c in range(count):
-				fns += ["%s%s%s" % (
+				fns += ['%s%s%s' % (
 					name,
-					(c+1 if count > 1 else ""),
-					('.in' if ch=='i' else '.out')
+					('',c+1)[count > 1],
+					('.out','.in')[ch=='i']
 				) for ch in io]
 		return fns
 
@@ -150,75 +180,85 @@ class MMGenToolTestSuite(object):
 		self.__class__.__dict__[cmd](*([self,cmd] + file_list))
 
 
-	def run_cmd(self,name,tool_args,kwargs="",extra_msg="",silent=False,strip=True):
-		mmgen_tool = "mmgen-tool"
+	def run_cmd(self,name,tool_args,kwargs='',extra_msg='',silent=False,strip=True):
+		mmgen_tool = 'mmgen-tool'
 		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()
-		if extra_msg: extra_msg = "(%s)" % extra_msg
-		full_name = " ".join([name]+kwargs.split()+extra_msg.split())
+		sys_cmd = ['python', mmgen_tool, '-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:
 			if opt.verbose:
-				sys.stderr.write(green("Testing %s\nExecuting " % full_name))
-				sys.stderr.write("%s\n" % cyan(repr(sys_cmd)))
+				sys.stderr.write(green('Testing %s\nExecuting ' % full_name))
+				sys.stderr.write('%s\n' % cyan(repr(sys_cmd)))
 			else:
-				msg_r("Testing %-31s%s" % (full_name+":",""))
+				msg_r('Testing %-31s%s' % (full_name+':',''))
 
 		import subprocess
-		ret = subprocess.check_output(sys_cmd)
-		return (ret,ret.rstrip())[int(strip)]
-
-	def run_cmd_chk(self,name,f1,f2,kwargs="",extra_msg=""):
+		p = subprocess.Popen(
+			sys_cmd,
+			stdout=subprocess.PIPE,
+			stderr=subprocess.PIPE,
+			)
+		a,b = p.communicate()
+		retcode = p.wait()
+		if retcode != 0:
+			msg('%s\n%s\n%s'%(red('FAILED'),yellow('Command stderr output:'),b))
+			die(1,red('Called process returned with an error (retcode %s)' % retcode))
+		return (a,a.rstrip())[bool(strip)]
+
+	def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg=''):
 		idata = read_from_file(f1).rstrip()
 		odata = read_from_file(f2).rstrip()
 		ret = self.run_cmd(name,[odata],kwargs=kwargs,extra_msg=extra_msg)
-		vmsg("In:   " + repr(odata))
-		vmsg("Out:  " + repr(ret))
+		vmsg('In:   ' + repr(odata))
+		vmsg('Out:  ' + repr(ret))
 		if ret == idata: ok()
 		else:
-			msg(red(
+			die(3,red(
 	"Error: values don't match:\nIn:  %s\nOut: %s" % (repr(idata),repr(ret))))
-			sys.exit(3)
 		return ret
 
-	def run_cmd_nochk(self,name,f1,kwargs=""):
+	def run_cmd_nochk(self,name,f1,kwargs=''):
 		odata = read_from_file(f1).rstrip()
 		ret = self.run_cmd(name,[odata],kwargs=kwargs)
-		vmsg("In:   " + repr(odata))
-		vmsg("Out:  " + repr(ret))
+		vmsg('In:   ' + repr(odata))
+		vmsg('Out:  ' + repr(ret))
 		return ret
 
-	def run_cmd_out(self,name,carg=None,Return=False,kwargs="",fn_idx="",extra_msg=""):
-		if carg: write_to_tmpfile(cfg,"%s%s.in" % (name,fn_idx),carg+"\n")
-		ret = self.run_cmd(name,[carg] if carg else [],kwargs=kwargs,extra_msg=extra_msg)
-		if carg: vmsg("In:   " + repr(carg))
-		vmsg("Out:  " + repr(ret))
+	def run_cmd_out(self,name,carg=None,Return=False,kwargs='',fn_idx='',extra_msg='',literal=False,chkdata=''):
+		if carg: write_to_tmpfile(cfg,'%s%s.in' % (name,fn_idx),carg+'\n')
+		ret = self.run_cmd(name,([],[carg])[bool(carg)],kwargs=kwargs,extra_msg=extra_msg)
+		if carg: vmsg('In:   ' + repr(carg))
+		vmsg('Out:  ' + (repr(ret),ret)[literal])
 		if ret:
-			write_to_tmpfile(cfg,"%s%s.out" % (name,fn_idx),ret+"\n")
+			write_to_tmpfile(cfg,'%s%s.out' % (name,fn_idx),ret+'\n')
+			if chkdata:
+				cmp_or_die(ret,chkdata)
+				return
 			if Return: return ret
 			else:   ok()
 		else:
-			msg(red("Error for command '%s'" % name))
-			sys.exit(3)
+			die(3,red("Error for command '%s'" % name))
 
 	def run_cmd_randinput(self,name,strip=True):
 		s = os.urandom(128)
-		fn = name+".in"
+		fn = name+'.in'
 		write_to_tmpfile(cfg,fn,s,binary=True)
 		ret = self.run_cmd(name,[get_tmpfile_fn(cfg,fn)],strip=strip)
-		fn = name+".out"
-		write_to_tmpfile(cfg,fn,ret+"\n")
+		fn = name+'.out'
+		write_to_tmpfile(cfg,fn,ret+'\n')
 		ok()
-		vmsg("Returned: %s" % ret)
+		vmsg('Returned: %s' % ret)
 
 	def str2id6(self,name):
 		s = getrandstr(120,no_space=True)
-		s2 = " %s %s %s %s %s " % (s[:3],s[3:9],s[9:29],s[29:50],s[50:120])
-		ret1 = self.run_cmd(name,[s],extra_msg="unspaced input"); ok()
-		ret2 = self.run_cmd(name,[s2],extra_msg="spaced input")
+		s2 = ' %s %s %s %s %s ' % (s[:3],s[3:9],s[9:29],s[29:50],s[50:120])
+		ret1 = self.run_cmd(name,[s],extra_msg='unspaced input'); ok()
+		ret2 = self.run_cmd(name,[s2],extra_msg='spaced input')
 		cmp_or_die(ret1,ret2)
-		vmsg("Returned: %s" % ret1)
+		vmsg('Returned: %s' % ret1)
 
 	def mn_rand128(self,name):
 		self.run_cmd_out(name)
@@ -246,9 +286,9 @@ class MMGenToolTestSuite(object):
 		cmp_or_die(orig,ret)
 
 	def rand2file(self,name):
-		of = name + ".out"
+		of = name + '.out'
 		dlen = 1024
-		self.run_cmd(name,[of,str(1024),"threads=4","silent=1"],strip=False)
+		self.run_cmd(name,[of,str(1024),'threads=4','silent=1'],strip=False)
 		d = read_from_tmpfile(cfg,of,binary=True)
 		cmp_or_die(dlen,len(d))
 
@@ -263,52 +303,63 @@ class MMGenToolTestSuite(object):
 	def b32tohex(self,name,f1,f2): self.run_cmd_chk(name,f1,f2)
 	def b58randenc(self,name):
 		ret = self.run_cmd_out(name,Return=True)
-		ok_or_die(ret,is_b58_str,"base 58 string")
+		ok_or_die(ret,is_b58_str,'base 58 string')
 	def randhex(self,name):
 		ret = self.run_cmd_out(name,Return=True)
-		ok_or_die(ret,binascii.unhexlify,"hex string")
+		ok_or_die(ret,binascii.unhexlify,'hex string')
 	def randwif(self,name):
-		for n,k in enumerate(["","compressed=1"]):
+		for n,k in enumerate(['','compressed=1']):
 			ret = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1)
-			ok_or_die(ret,is_wif,"WIF key")
+			ok_or_die(ret,is_wif,'WIF key')
 	def randpair(self,name):
-		for n,k in enumerate(["","compressed=1"]):
+		for n,k in enumerate(['','compressed=1']):
 			wif,addr = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1).split()
-			ok_or_die(wif,is_wif,"WIF key",skip_ok=True)
-			ok_or_die(addr,is_btc_addr,"Bitcoin address")
+			ok_or_die(wif,is_wif,'WIF key',skip_ok=True)
+			ok_or_die(addr,is_btc_addr,'Bitcoin address')
 	def hex2wif(self,name,f1,f2,f3,f4):
-		for n,fi,fo,k in (1,f1,f2,""),(2,f3,f4,"compressed=1"):
+		for n,fi,fo,k in (1,f1,f2,''),(2,f3,f4,'compressed=1'):
 			ret = self.run_cmd_chk(name,fi,fo,kwargs=k)
 	def wif2hex(self,name,f1,f2):
-		for n,f,k in (1,f1,""),(2,f2,"compressed=1"):
+		for n,f,k in (1,f1,''),(2,f2,'compressed=1'):
 			wif = read_from_file(f).split()[0]
 			self.run_cmd_out(name,wif,kwargs=k,fn_idx=n)
 	def wif2addr(self,name,f1,f2):
-		for n,f,k in (1,f1,""),(2,f2,"compressed=1"):
+		for n,f,k in (1,f1,''),(2,f2,'compressed=1'):
 			wif = read_from_file(f).split()[0]
 			self.run_cmd_out(name,wif,kwargs=k,fn_idx=n)
 	def addr2hexaddr(self,name,f1,f2):
-		for n,f,m in (1,f1,""),(2,f2,"from compressed"):
+		for n,f,m in (1,f1,''),(2,f2,'from compressed'):
 			addr = read_from_file(f).split()[-1]
 			self.run_cmd_out(name,addr,fn_idx=n,extra_msg=m)
 	def hexaddr2addr(self,name,f1,f2,f3,f4):
-		for n,fi,fo,m in (1,f1,f2,""),(2,f3,f4,"from compressed"):
+		for n,fi,fo,m in (1,f1,f2,''),(2,f3,f4,'from compressed'):
 			self.run_cmd_chk(name,fi,fo,extra_msg=m)
 	def privhex2addr(self,name,f1,f2):
 		key1 = read_from_file(f1).rstrip()
 		key2 = read_from_file(f2).rstrip()
-		for n,args in enumerate([[key1],[key2,"compressed=1"]]):
+		for n,args in enumerate([[key1],[key2,'compressed=1']]):
 			ret = self.run_cmd(name,args).rstrip()
-			iaddr = read_from_tmpfile(cfg,"randpair%s.out" % (n+1)).split()[-1]
+			iaddr = read_from_tmpfile(cfg,'randpair%s.out' % (n+1)).split()[-1]
 			cmp_or_die(iaddr,ret)
 	def hex2mn(self,name):
-		for n,size,m in(1,16,"128-bit"),(2,24,"192-bit"),(3,32,"256-bit"):
+		for n,size,m in(1,16,'128-bit'),(2,24,'192-bit'),(3,32,'256-bit'):
 			hexnum = getrandhex(size)
 			self.run_cmd_out(name,hexnum,fn_idx=n,extra_msg=m)
 	def mn2hex(self,name,f1,f2,f3,f4,f5,f6):
-		for f_i,f_o,m in (f1,f2,"128-bit"),(f3,f4,"192-bit"),(f5,f6,"256-bit"):
+		for f_i,f_o,m in (f1,f2,'128-bit'),(f3,f4,'192-bit'),(f5,f6,'256-bit'):
 			self.run_cmd_chk(name,f_i,f_o,extra_msg=m)
 
+	def getbalance(self,name):
+		self.run_cmd_out(name,literal=True)
+	def listaddresses(self,name):
+		self.run_cmd_out(name,literal=True)
+	def txview(self,name):
+		fn = os.path.join(cfg['refdir'],cfg['txfile'])
+		self.run_cmd_out(name,fn,literal=True)
+	def addrfile_chksum(self,name):
+		fn = os.path.join(cfg['refdir'],cfg['addrfile'])
+		self.run_cmd_out(name,fn,literal=True,chkdata=cfg['addrfile_chk'])
+
 # main()
 import time
 start_time = int(time.time())
@@ -317,26 +368,24 @@ mk_tmpdir(cfg)
 
 if cmd_args:
 	if len(cmd_args) != 1:
-		msg("Only one command may be specified")
-		sys.exit(1)
+		die(1,'Only one command may be specified')
 
 	cmd = cmd_args[0]
 	if cmd in cmd_data:
-		msg("Running tests for %s:" % cmd_data[cmd]['desc'])
+		msg('Running tests for %s:' % cmd_data[cmd]['desc'])
 		ts.do_cmds(cmd)
-	elif cmd == "clean":
+	elif cmd == 'clean':
 		cleandir(cfg['tmpdir'])
-		sys.exit(0)
+		sys.exit()
 	else:
-		msg("'%s': unrecognized command" % cmd)
-		sys.exit(1)
+		die(1,"'%s': unrecognized command" % cmd)
 else:
 	cleandir(cfg['tmpdir'])
 	for cmd in cmd_data:
-		msg("Running tests for %s:" % cmd_data[cmd]['desc'])
+		msg('Running tests for %s:' % cmd_data[cmd]['desc'])
 		ts.do_cmds(cmd)
-		if cmd is not cmd_data.keys()[-1]: msg("")
+		if cmd is not cmd_data.keys()[-1]: msg('')
 
 t = int(time.time()) - start_time
 msg(green(
-	"All requested tests finished OK, elapsed time: %02i:%02i" % (t/60,t%60)))
+	'All requested tests finished OK, elapsed time: %02i:%02i' % (t/60,t%60)))

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