Browse Source

TX scripts updates; import address feature added
new file: mmgen-addrimport
new file: mmgen/connection.py
new file: mmgen/proxy.py

philemon 11 years ago
parent
commit
aa66a44066
19 changed files with 1278 additions and 216 deletions
  1. 3 0
      MANIFEST
  2. 0 1
      mmgen-addrgen
  3. 173 0
      mmgen-addrgen
  4. 69 0
      mmgen-addrimport
  5. 0 174
      mmgen-keygen
  6. 1 0
      mmgen-keygen
  7. 3 2
      mmgen-passchg
  8. 16 12
      mmgen-txcreate
  9. 1 1
      mmgen-txsend
  10. 1 1
      mmgen-txsign
  11. 3 3
      mmgen-walletchk
  12. 23 14
      mmgen/bitcoin.py
  13. 737 0
      mmgen/connection.py
  14. 130 0
      mmgen/proxy.py
  15. 32 3
      mmgen/tx.py
  16. 63 2
      mmgen/utils.py
  17. 3 0
      scripts/deinstall.sh
  18. 15 3
      setup.py
  19. 5 0
      tests/bitcoin.py

+ 3 - 0
MANIFEST

@@ -1,8 +1,10 @@
 # file GENERATED by distutils, do NOT edit
 # file GENERATED by distutils, do NOT edit
 mmgen-addrgen
 mmgen-addrgen
+mmgen-addrimport
 mmgen-keygen
 mmgen-keygen
 mmgen-passchg
 mmgen-passchg
 mmgen-txcreate
 mmgen-txcreate
+mmgen-txsend
 mmgen-txsign
 mmgen-txsign
 mmgen-walletchk
 mmgen-walletchk
 mmgen-walletgen
 mmgen-walletgen
@@ -12,6 +14,7 @@ mmgen/__init__.py
 mmgen/addr.py
 mmgen/addr.py
 mmgen/bitcoin.py
 mmgen/bitcoin.py
 mmgen/config.py
 mmgen/config.py
+mmgen/connection.py
 mmgen/license.py
 mmgen/license.py
 mmgen/mn_electrum.py
 mmgen/mn_electrum.py
 mmgen/mn_tirosh.py
 mmgen/mn_tirosh.py

+ 0 - 1
mmgen-addrgen

@@ -1 +0,0 @@
-mmgen-keygen

+ 173 - 0
mmgen-addrgen

@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C) 2013 by 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-addrgen: Generate a range of addresses from a mmgen deterministic 
+               wallet.
+               Call as 'btc-keygen' to allow key generation as well.
+"""
+
+import sys
+
+from mmgen.Opts   import *
+from mmgen.config import *
+from mmgen.license import *
+from mmgen.utils  import *
+from mmgen.addr   import *
+
+invoked_as = sys.argv[0].split("-")[-1]
+gen_what = "addresses" if invoked_as == "addrgen" else "keys"
+
+if invoked_as == "keygen":
+	extra_help_data = (
+		"\n-A, --no-addresses       Print only secret keys, no addresses",
+		"\n-x, --b16                Print secret keys in hexadecimal too",
+		".\nBy default, both addresses and secret keys are generated"
+	)
+else: extra_help_data = ("","","")
+
+help_data = {
+	'prog_name': sys.argv[0].split("/")[-1],
+	'desc': """Generate a range of {} from a {} wallet, mnemonic,
+                  seed or password""".format(gen_what,proj_name),
+	'usage':"[opts] [infile] <address range>",
+	'options': """
+-h, --help               Print this help message{}
+-d, --outdir          d  Specify an alternate directory 'd' for output
+-e, --echo-passphrase    Display passphrase or mnemonic on screen upon entry
+-K, --no-keyconv         Use internal libraries for address generation
+                         instead of 'keyconv' 
+-l, --seed-len        N  Length of seed.  Options: {}
+                         (default: {})
+-p, --hash-preset     p  Use scrypt.hash() parameters from preset 'p'
+                         when hashing password (default: '{}')
+-P, --show-hash-presets  Show information on available hash presets
+-q, --quiet              Suppress warnings; overwrite files without asking
+-S, --stdout             Print {W} to stdout
+-v, --verbose            Produce more verbose output{}
+
+-b, --from-brain     l,p Generate {W} from a user-created password,
+                         i.e. a "brainwallet", using seed length 'l' and
+                         hash preset 'p' (comma-separated)
+-m, --from-mnemonic      Generate {W} from an electrum-like mnemonic
+-s, --from-seed          Generate {W} from a seed in .{S} format
+
+Address range may be a single number or a range in the form XXX-YYY{}
+
+If available, external 'keyconv' program will be used for address generation
+
+Data for the --from-<what> options will be taken from <infile> if <infile>
+is specified.  Otherwise, the user will be prompted to enter the data.  Note
+that passphrase data in a file may be arranged in free-form fashion, using
+any combination of spaces, tabs or newlines to separate words
+
+BRAINWALLET NOTE:
+
+As brainwallets require especially strong hashing to thwart dictionary
+attacks, the brainwallet hash preset must be specified by the user, using
+the 'p' parameter of the '--from-brain' option
+
+The '--from-brain' option also requires the user to specify a seed length
+(the 'l' parameter)
+
+For a brainwallet passphrase to always generate the same keys and addresses,
+the same 'l' and 'p' parameters to '--from-brain' must be used in all future
+invocations with that passphrase
+""".format(
+		extra_help_data[0],
+		", ".join([str(i) for i in seed_lens]),
+		seed_len,
+		hash_preset,
+		extra_help_data[1],
+		extra_help_data[2],
+		W=gen_what,
+		S=seed_ext
+	)
+}
+
+so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s"
+lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\
+		"seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\
+		"verbose","b16","from_brain=","from_mnemonic","from_seed"
+
+if invoked_as == "addrgen":
+	short_opts = so[0:1] + so[2:10] + so[11:]
+	long_opts  = lo[0:1] + lo[2:10] + lo[11:]
+else:
+	short_opts,long_opts = so,lo
+
+opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
+
+if 'show_hash_presets' in opts: show_hash_presets()
+
+# Sanity checking and processing of command-line arguments:
+
+opts['gen_what'] = gen_what
+
+set_if_unset_and_typeconvert(opts,(
+	('hash_preset',hash_preset,'str'),
+	('seed_len',seed_len,'int')
+))
+
+# Exits on invalid input
+check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
+
+if debug:
+	print "Processed options:     %s" % repr(opts)
+	print "Cmd args:              %s" % repr(cmd_args)
+
+if   len(cmd_args) == 1 and (
+			'from_mnemonic' in opts or
+			'from_brain' in opts or
+			'from_seed' in opts
+		): infile,addr_range = "",cmd_args[0]
+elif len(cmd_args) == 2:
+	infile,addr_range = cmd_args
+	check_infile(infile)
+else: usage(help_data)
+
+start,end = parse_address_range(addr_range)
+
+if not 'quiet' in opts: do_license_msg()
+
+# Interact with user:
+if invoked_as == "keygen" and not 'quiet' in opts:
+	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
+
+# Generate data:
+
+if invoked_as == "addrgen":
+	opts['print_addresses_only'] = True
+else:
+	if not 'no_addresses' in opts: opts['print_secret'] = True
+
+seed          = get_seed(infile,opts)
+seed_id       = make_chksum_8(seed)
+addr_data     = generate_addrs(seed, start, end, opts)
+addr_data_str = format_addr_data(addr_data, seed_id, opts)
+
+# Output data:
+if 'stdout' in opts:
+	if invoked_as == "keygen" and not 'quiet' in opts:
+		confirm = True
+	else: confirm = False
+	write_to_stdout(addr_data_str,"secret keys",confirm)
+elif not sys.stdout.isatty():
+	write_to_stdout(addr_data_str,"secret keys",confirm=False)
+else:
+	write_addr_data_to_file(seed, addr_data_str, start, end, opts)

+ 69 - 0
mmgen-addrimport

@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C) 2013 by 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-addrimport: Import addresses generated by mmgen-addrgen into an
+                  online bitcoind watching wallet.
+"""
+
+import sys
+from mmgen.Opts   import *
+from mmgen.config import *
+from mmgen.license import *
+from mmgen.utils import check_infile,parse_addrs_file,confirm_or_exit
+
+help_data = {
+	'prog_name': sys.argv[0].split("/")[-1],
+	'desc': """Import addresses generated by mmgen-addrgen into an
+                     online bitcoind watching wallet""",
+	'usage':"[opts] [infile]",
+	'options': """
+-h, --help               Print this help message
+-q, --quiet              Suppress warnings
+-v, --verbose            Produce more verbose output
+"""
+}
+
+short_opts = "hqv"
+long_opts  = "help", "quiet", "verbose"
+
+opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
+
+if len(cmd_args) != 1: usage(help_data)
+
+check_infile(cmd_args[0])
+
+seed_id,addr_data = parse_addrs_file(cmd_args[0])
+
+from mmgen.tx import connect_to_bitcoind
+c = connect_to_bitcoind(mmgen=True)
+
+message = """
+Importing addresses can take a long time, up to 30 min. per address on a
+low-powered computer such as a netbook.
+"""
+confirm_or_exit(message, "continue", expect="YES")
+
+for n,i in enumerate(addr_data):
+	comment = " " + " ".join(i[2:]) if len(i) > 2 else ""
+	label = "%s:%s%s" % (seed_id,str(i[0]),comment)
+	msg("Importing %-6s %-34s (%s)" % (
+				("%s/%s:" % (n+1,len(addr_data))),
+				i[1], label)
+			)
+ 	c.importaddress(i[1],label)

+ 0 - 174
mmgen-keygen

@@ -1,174 +0,0 @@
-#!/usr/bin/env python
-#
-# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C) 2013 by 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-keygen: Generate addresses/secret keys from a mmgen deterministic 
-              wallet for a range of addresses.
-              Call as 'btc-addrgen' for safe, reduced functionality
-              with no output of secret keys.
-"""
-
-import sys
-
-from mmgen.Opts   import *
-from mmgen.config import *
-from mmgen.license import *
-from mmgen.utils  import *
-from mmgen.addr   import *
-
-invoked_as = sys.argv[0].split("-")[-1]
-gen_what = "addresses" if invoked_as == "addrgen" else "keys"
-
-if invoked_as == "keygen":
-	extra_help_data = (
-		"\n-A, --no-addresses       Print only secret keys, no addresses",
-		"\n-x, --b16                Print secret keys in hexadecimal too",
-		".\nBy default, both addresses and secret keys are generated"
-	)
-else: extra_help_data = ("","","")
-
-help_data = {
-	'prog_name': sys.argv[0].split("/")[-1],
-	'desc': """Generate a range of {} from a {} wallet, mnemonic,
-                  seed or password""".format(gen_what,proj_name),
-	'usage':"[opts] [infile] <address range>",
-	'options': """
--h, --help               Print this help message{}
--d, --outdir          d  Specify an alternate directory 'd' for output
--e, --echo-passphrase    Display passphrase or mnemonic on screen upon entry
--K, --no-keyconv         Use internal libraries for address generation
-                         instead of 'keyconv' 
--l, --seed-len        N  Length of seed.  Options: {}
-                         (default: {})
--p, --hash-preset     p  Use scrypt.hash() parameters from preset 'p'
-                         when hashing password (default: '{}')
--P, --show-hash-presets  Show information on available hash presets
--q, --quiet              Suppress warnings; overwrite files without asking
--S, --stdout             Print {W} to stdout
--v, --verbose            Produce more verbose output{}
-
--b, --from-brain     l,p Generate {W} from a user-created password,
-                         i.e. a "brainwallet", using seed length 'l' and
-                         hash preset 'p' (comma-separated)
--m, --from-mnemonic      Generate {W} from an electrum-like mnemonic
--s, --from-seed          Generate {W} from a seed in .{S} format
-
-Address range may be a single number or a range in the form XXX-YYY{}
-
-If available, external 'keyconv' program will be used for address generation
-
-Data for the --from-<what> options will be taken from <infile> if <infile>
-is specified.  Otherwise, the user will be prompted to enter the data.  Note
-that passphrase data in a file may be arranged in free-form fashion, using
-any combination of spaces, tabs or newlines to separate words
-
-BRAINWALLET NOTE:
-
-As brainwallets require especially strong hashing to thwart dictionary
-attacks, the brainwallet hash preset must be specified by the user, using
-the 'p' parameter of the '--from-brain' option
-
-The '--from-brain' option also requires the user to specify a seed length
-(the 'l' parameter)
-
-For a brainwallet passphrase to always generate the same keys and addresses,
-the same 'l' and 'p' parameters to '--from-brain' must be used in all future
-invocations with that passphrase
-""".format(
-		extra_help_data[0],
-		", ".join([str(i) for i in seed_lens]),
-		seed_len,
-		hash_preset,
-		extra_help_data[1],
-		extra_help_data[2],
-		W=gen_what,
-		S=seed_ext
-	)
-}
-
-so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s"
-lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\
-		"seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\
-		"verbose","b16","from_brain=","from_mnemonic","from_seed"
-
-if invoked_as == "addrgen":
-	short_opts = so[0:1] + so[2:10] + so[11:]
-	long_opts  = lo[0:1] + lo[2:10] + lo[11:]
-else:
-	short_opts,long_opts = so,lo
-
-opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
-
-if 'show_hash_presets' in opts: show_hash_presets()
-
-# Sanity checking and processing of command-line arguments:
-
-opts['gen_what'] = gen_what
-
-set_if_unset_and_typeconvert(opts,(
-	('hash_preset',hash_preset,'str'),
-	('seed_len',seed_len,'int')
-))
-
-# Exits on invalid input
-check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
-
-if debug:
-	print "Processed options:     %s" % repr(opts)
-	print "Cmd args:              %s" % repr(cmd_args)
-
-if   len(cmd_args) == 1 and (
-			'from_mnemonic' in opts or
-			'from_brain' in opts or
-			'from_seed' in opts
-		): infile,addr_range = "",cmd_args[0]
-elif len(cmd_args) == 2:
-	infile,addr_range = cmd_args
-	check_infile(infile)
-else: usage(help_data)
-
-start,end = parse_address_range(addr_range)
-
-if not 'quiet' in opts: do_license_msg()
-
-# Interact with user:
-if invoked_as == "keygen" and not 'quiet' in opts:
-	confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
-
-# Generate data:
-
-if invoked_as == "addrgen":
-	opts['print_addresses_only'] = True
-else:
-	if not 'no_addresses' in opts: opts['print_secret'] = True
-
-seed          = get_seed(infile,opts)
-seed_id       = make_chksum_8(seed)
-addr_data     = generate_addrs(seed, start, end, opts)
-addr_data_str = format_addr_data(addr_data, seed_id, opts)
-
-# Output data:
-if 'stdout' in opts:
-	if invoked_as == "keygen" and not 'quiet' in opts:
-		confirm = True
-	else: confirm = False
-	write_to_stdout(addr_data_str,"secret keys",confirm)
-elif not sys.stdout.isatty():
-	write_to_stdout(addr_data_str,"secret keys",confirm=False)
-else:
-	write_addr_data_to_file(seed, addr_data_str, start, end, opts)

+ 1 - 0
mmgen-keygen

@@ -0,0 +1 @@
+mmgen-addrgen

+ 3 - 2
mmgen-passchg

@@ -35,7 +35,7 @@ help_data = {
 -d, --outdir             d  Specify an alternate directory 'd' for output
 -d, --outdir             d  Specify an alternate directory 'd' for output
 -k, --keep-old-passphrase   Keep old passphrase (use when changing hash 
 -k, --keep-old-passphrase   Keep old passphrase (use when changing hash 
                             strength or label only)
                             strength or label only)
--L, --label                 Change the wallet's label
+-L, --label              l  Change the wallet's label to 'l'
 -p, --hash-preset        p  Change scrypt.hash() parameters to preset 'p'
 -p, --hash-preset        p  Change scrypt.hash() parameters to preset 'p'
                             (default: '{}')
                             (default: '{}')
 -P, --show-hash-presets     Show information on available hash presets
 -P, --show-hash-presets     Show information on available hash presets
@@ -65,7 +65,8 @@ infile = cmd_args[0]
 # Old key:
 # Old key:
 label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
 label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
 seed_id,key_id = metadata[:2]
 seed_id,key_id = metadata[:2]
-passwd = " ".join(get_words("","","Enter old passphrase: ",opts))
+oldp = "" if 'keep_old_passphrase' in opts else "old "
+passwd = " ".join(get_words("","",("Enter %spassphrase: " % oldp),opts))
 key = make_key(passwd, salt, hash_preset)
 key = make_key(passwd, salt, hash_preset)
 seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 seed = decrypt_seed(enc_seed, key, seed_id, key_id)
 
 

+ 16 - 12
mmgen-txcreate

@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 """
-mmgen-txcreate: Send BTC from specified outputs to specified addresses
+mmgen-txcreate: Send BTC to specified addresses
 """
 """
 
 
 import sys
 import sys
@@ -33,8 +33,8 @@ prog_name = sys.argv[0].split("/")[-1]
 
 
 help_data = {
 help_data = {
 	'prog_name': prog_name,
 	'prog_name': prog_name,
-	'desc':    "Send BTC from specified outputs to specified addresses",
-	'usage':   "[opts] <recipient address> <amount> <transaction fee> <change address>",
+	'desc':    "Send BTC to specified addresses",
+	'usage':   "[opts] <address:amount>[,...] <transaction fee> <change address>",
 	'options': """
 	'options': """
 -h, --help                Print this help message
 -h, --help                Print this help message
 -d, --outdir           d  Specify an alternate directory 'd' for output
 -d, --outdir           d  Specify an alternate directory 'd' for output
@@ -42,6 +42,8 @@ help_data = {
 -i, --info                Display unspent outputs and exit
 -i, --info                Display unspent outputs and exit
 -q, --quiet               Suppress warnings; overwrite files without asking
 -q, --quiet               Suppress warnings; overwrite files without asking
 
 
+Outputs to spend are chosen by the user via a menu.
+
 Ages of transactions are approximate based on an estimated block discovery
 Ages of transactions are approximate based on an estimated block discovery
 time of %s minutes.
 time of %s minutes.
 """ % mins_per_block
 """ % mins_per_block
@@ -59,19 +61,20 @@ if debug:
 	print "Processed options:     %s" % repr(opts)
 	print "Processed options:     %s" % repr(opts)
 	print "Cmd args:              %s" % repr(cmd_args)
 	print "Cmd args:              %s" % repr(cmd_args)
 
 
-if len(cmd_args) == 4:
-	rcpt_addr,send_amt,tx_fee,change_addr = cmd_args	
+if len(cmd_args) == 3:
+	rcpt_arg,tx_fee,change_addr = cmd_args	
 	check_address(change_addr)
 	check_address(change_addr)
-elif len(cmd_args) == 3:
-	rcpt_addr,send_amt,tx_fee = cmd_args	
+elif len(cmd_args) == 2:
+	rcpt_arg,tx_fee = cmd_args	
 	change_addr = ""
 	change_addr = ""
 elif len(cmd_args) == 0 and 'info' in opts:
 elif len(cmd_args) == 0 and 'info' in opts:
 	pass
 	pass
 else: usage(help_data)
 else: usage(help_data)
 
 
 if not 'info' in opts:
 if not 'info' in opts:
-	check_address(rcpt_addr)
-	send_amt = check_btc_amt(send_amt)
+	tx_out = make_tx_out(rcpt_arg)
+	for i in tx_out.keys():   check_address(i)
+	for i in tx_out.values(): check_btc_amt(i)
 	tx_fee   = check_btc_amt(tx_fee)
 	tx_fee   = check_btc_amt(tx_fee)
 
 
 # Begin execution
 # Begin execution
@@ -86,10 +89,10 @@ total = trim_exponent(sum([i.amount for i in unspent]))
 msg("Total unspent:   %s BTC" % total)
 msg("Total unspent:   %s BTC" % total)
 if 'info' in opts: sys.exit(0)
 if 'info' in opts: sys.exit(0)
 
 
-msg("Amount to spend: %s BTC" % send_amt)
+send_amt = sum(tx_out.values())
+msg("Total amount to spend: %s BTC" % send_amt)
 msg("%s unspent outputs total" % len(unspent))
 msg("%s unspent outputs total" % len(unspent))
 
 
-
 while True:
 while True:
 	sel_unspent = select_outputs(unspent,"Choose the outputs to spend: ")
 	sel_unspent = select_outputs(unspent,"Choose the outputs to spend: ")
 	total_in = trim_exponent(sum([o.amount for o in sel_unspent]))
 	total_in = trim_exponent(sum([o.amount for o in sel_unspent]))
@@ -107,7 +110,8 @@ if change > 0 and not change_addr:
 	sys.exit(2)
 	sys.exit(2)
 
 
 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]
-tx_out = {rcpt_addr:float(send_amt), change_addr:float(change)}
+for i in tx_out.keys(): tx_out[i] = float(tx_out[i])
+if change: tx_out[change_addr] = float(change)
 tx_hex = c.createrawtransaction(tx_in,tx_out)
 tx_hex = c.createrawtransaction(tx_in,tx_out)
 
 
 msg("Transaction successfully created")
 msg("Transaction successfully created")

+ 1 - 1
mmgen-txsend

@@ -83,7 +83,7 @@ confirm_or_exit(warn, what, expect)
 
 
 msg("Sending transaction")
 msg("Sending transaction")
 
 
-c = connect_to_bitcoind()
+c = connect_to_bitcoind(mmgen=True)
 
 
 try:
 try:
 	tx = c.sendrawtransaction(tx_sig)
 	tx = c.sendrawtransaction(tx_sig)

+ 1 - 1
mmgen-txsign

@@ -118,7 +118,7 @@ if wallet_enc:
 if sig_tx['complete']:
 if sig_tx['complete']:
 	msg("Signing completed")
 	msg("Signing completed")
 else:
 else:
-	msg("signrawtransaction() returned failure")
+	msg("Signing failed: 'complete=%s'" % sig_tx['complete'])
 	sys.exit(3)
 	sys.exit(3)
 
 
 prompt = "Save signed transaction?"
 prompt = "Save signed transaction?"

+ 3 - 3
mmgen-walletchk

@@ -49,9 +49,9 @@ opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
 
 
 check_opts(opts, ('outdir',))
 check_opts(opts, ('outdir',))
 
 
-if len(cmd_args) != 1:
-	msg("One input file must be specified")
-	sys.exit(2)
+if len(cmd_args) != 1: usage(help_data)
+
+check_infile(cmd_args[0])
 
 
 if 'export_mnemonic' in opts:
 if 'export_mnemonic' in opts:
 	msg("Exporting mnemonic data to file by user request")
 	msg("Exporting mnemonic data to file by user request")

+ 23 - 14
mmgen/bitcoin.py

@@ -51,7 +51,7 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
 # The "zero address":
 # The "zero address":
 # 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate)
 # 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate)
 #
 #
-def _pubhex2addr(pubhex):
+def pubhex2addr(pubhex):
 	step1 = sha256(unhexlify(pubhex)).digest()
 	step1 = sha256(unhexlify(pubhex)).digest()
 	step2 = hashlib_new('ripemd160',step1).hexdigest()
 	step2 = hashlib_new('ripemd160',step1).hexdigest()
 	# See above:
 	# See above:
@@ -64,28 +64,32 @@ def _pubhex2addr(pubhex):
 def privnum2addr(numpriv):
 def privnum2addr(numpriv):
 	pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
 	pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
 	pubkey = hexlify(pko.get_verifying_key().to_string())
 	pubkey = hexlify(pko.get_verifying_key().to_string())
-	return _pubhex2addr('04'+pubkey)
+	return pubhex2addr('04'+pubkey)
 
 
 def verify_addr(addr):
 def verify_addr(addr):
 
 
 	if addr[0] != "1":
 	if addr[0] != "1":
-		print "%s Invalid address" % addr
+		print "%s: Invalid address" % addr
 		return False
 		return False
 
 
-	addr,lz = addr[1:],0
-	while addr[0] == "1": addr = addr[1:]; lz += 1
-		
-	addr_hex = lz * "00" + hex(_b58tonum(addr))[2:].rstrip("L")
+# 	addr,lz = addr[1:],0
+# 	while addr[0] == "1": addr = addr[1:]; lz += 1
+# 		
+#  	addr_hex = lz * "00" + hex(_b58tonum(addr))[2:].rstrip("L")
 
 
-	if len(addr_hex) != 48:
-		print "%s Invalid address" % addr
-		return False
+# 	if len(addr_hex) != 48:
+# 		print "%s: Invalid address hex length: %s" % ("1"+addr, len(addr_hex))
+# 		return False
+
+  	num = _b58tonum(addr[1:])
+	if num == False: return False
+  	addr_hex = hex(num)[2:].rstrip("L").zfill(48)
 
 
 	step1 = sha256(unhexlify('00'+addr_hex[:40])).digest()
 	step1 = sha256(unhexlify('00'+addr_hex[:40])).digest()
 	step2 = sha256(step1).hexdigest()
 	step2 = sha256(step1).hexdigest()
 
 
 	if step2[:8] != addr_hex[40:]:
 	if step2[:8] != addr_hex[40:]:
-		print "Invalid checksum in address %s" % addr
+		print "Invalid checksum in address %s" % ("1" + addr)
 		return False
 		return False
 
 
 	return True
 	return True
@@ -104,7 +108,7 @@ def _b58tonum(b58num):
 	for i in b58num:
 	for i in b58num:
 		if not i in b58a:
 		if not i in b58a:
 			print "Invalid symbol in b58 number: '%s'" % i
 			print "Invalid symbol in b58 number: '%s'" % i
-			sys.exit(9)
+			return False
 
 
 	b58deconv = []
 	b58deconv = []
 	b58num_r = b58num[::-1]
 	b58num_r = b58num[::-1]
@@ -135,6 +139,7 @@ def b58decode(b58num):
 	if b58num == "": return ""
 	if b58num == "": return ""
 	# Zap all spaces:
 	# Zap all spaces:
 	num = _b58tonum(b58num.translate(None,' \t\n\r'))
 	num = _b58tonum(b58num.translate(None,' \t\n\r'))
+	if num == False: return False
 	out = hex(num)[2:].rstrip('L')
 	out = hex(num)[2:].rstrip('L')
 	return unhexlify("0" + out if len(out) % 2 else out)
 	return unhexlify("0" + out if len(out) % 2 else out)
 
 
@@ -149,9 +154,10 @@ def _b58_pad(s,a,b,pad,f,w):
 	except:
 	except:
 		print "_b58_pad() accepts only %s %s bytes long "\
 		print "_b58_pad() accepts only %s %s bytes long "\
 			"(input was %s bytes)" % (w,",".join([str(i) for i in a]),len(s))
 			"(input was %s bytes)" % (w,",".join([str(i) for i in a]),len(s))
-		sys.exit(9)
+		return False
 
 
 	out = f(s)
 	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):
 def b58encode_pad(s):
@@ -168,10 +174,13 @@ def b58decode_pad(s):
 # To check validity, recode with numtowif()
 # To check validity, recode with numtowif()
 def wiftonum(wifpriv):
 def wiftonum(wifpriv):
 	num = _b58tonum(wifpriv)
 	num = _b58tonum(wifpriv)
+	if num == False: return False
 	return (num % (1<<288)) >> 32
 	return (num % (1<<288)) >> 32
 
 
 def wiftohex(wifpriv):
 def wiftohex(wifpriv):
-	key = hex(_b58tonum(wifpriv))[2:].rstrip('L')
+	num = _b58tonum(wifpriv)
+	if num == False: return False
+	key = hex(num)[2:].rstrip('L')
 	round1 = sha256(unhexlify(key[:66])).digest()
 	round1 = sha256(unhexlify(key[:66])).digest()
 	round2 = sha256(round1).hexdigest()
 	round2 = sha256(round1).hexdigest()
 	return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False
 	return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False

+ 737 - 0
mmgen/connection.py

@@ -0,0 +1,737 @@
+# 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.proxy import JSONRPCException, AuthServiceProxy
+from bitcoinrpc.exceptions import (_wrap_exception, WalletPassphraseIncorrect,
+			WalletAlreadyUnlocked)
+from bitcoinrpc.data import (ServerInfo, AccountInfo, AddressInfo,
+			TransactionInfo, AddressValidation, WorkItem, MiningInfo)
+
+
+class MMGenBitcoinConnection(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):
+		"""
+		"""
+		try:
+			return self.proxy.importaddress(address,label)
+		except JSONRPCException as e:
+			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 getbalance(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:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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, 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))
+#             else:
+#                 return self.proxy.listaccounts(minconf).keys()
+#         except JSONRPCException as e:
+#             raise _wrap_exception(e.error)
+# 
+#     def listreceivedbyaccount(self, minconf=1, includeempty=False):
+#         """
+#         Returns a list of accounts.
+# 
+#         Each account is represented with a :class:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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:`~bitcoinrpc.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 `~bitcoinrpc.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 `~bitcoinrpc.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

+ 130 - 0
mmgen/proxy.py

@@ -0,0 +1,130 @@
+"""
+  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"
+
+HTTP_TIMEOUT = 7200
+
+
+class JSONRPCException(Exception):
+    def __init__(self, rpcError):
+        Exception.__init__(self)
+        self.error = rpcError
+
+
+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)
+        if self.__url.scheme == 'https':
+            self.__conn = httplib.HTTPSConnection(self.__url.hostname, port, None, None,False,
+                                             HTTP_TIMEOUT)
+        else:
+            self.__conn = httplib.HTTPConnection(self.__url.hostname, port, False,
+                                             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})
+         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)
+         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

+ 32 - 3
mmgen/tx.py

@@ -22,7 +22,6 @@ tx.py:  Bitcoin transaction routines
 from binascii import unhexlify
 from binascii import unhexlify
 from mmgen.utils import msg,msg_r,write_to_file,my_raw_input,get_char,make_chksum_8,make_timestamp
 from mmgen.utils import msg,msg_r,write_to_file,my_raw_input,get_char,make_chksum_8,make_timestamp
 import sys, os
 import sys, os
-from bitcoinrpc.connection import *
 from decimal import Decimal
 from decimal import Decimal
 from mmgen.config import *
 from mmgen.config import *
 
 
@@ -36,13 +35,21 @@ specified recipient address.
 """.strip()
 """.strip()
 }
 }
 
 
-def connect_to_bitcoind():
+
+def connect_to_bitcoind(mmgen=False):
 
 
 	host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
 	host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
 	cfg = get_cfg_options((user,passwd))
 	cfg = get_cfg_options((user,passwd))
 
 
+	if mmgen:
+		import mmgen.connection
+		f = mmgen.connection.MMGenBitcoinConnection
+	else:
+		import bitcoinrpc.connection
+		f = bitcoinrpc.connection.BitcoinConnection
+
 	try:
 	try:
-		c = BitcoinConnection(cfg[user],cfg[passwd],host,port)
+		c = f(cfg[user],cfg[passwd],host,port)
 	except:
 	except:
 		msg("Unable to establish RPC connection with bitcoind")
 		msg("Unable to establish RPC connection with bitcoind")
 		sys.exit(2)
 		sys.exit(2)
@@ -55,11 +62,13 @@ def trim_exponent(d):
 	'''
 	'''
 	return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
 	return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
 
 
+
 def	check_address(rcpt_address):
 def	check_address(rcpt_address):
 	from mmgen.bitcoin import verify_addr
 	from mmgen.bitcoin import verify_addr
 	if not verify_addr(rcpt_address):
 	if not verify_addr(rcpt_address):
 		sys.exit(3)
 		sys.exit(3)
 
 
+
 def check_btc_amt(send_amt):
 def check_btc_amt(send_amt):
 
 
 	from decimal import Decimal
 	from decimal import Decimal
@@ -298,3 +307,23 @@ def select_outputs(unspent,prompt):
 		msg("'%s': Invalid input" % reply)
 		msg("'%s': Invalid input" % reply)
 
 
 	return [unspent[i] for i in selected]
 	return [unspent[i] for i in selected]
+
+
+def make_tx_out(rcpt_arg):
+
+	import decimal
+	try:
+		tx_out = dict([(i.split(":")[0],i.split(":")[1])
+							for i in rcpt_arg.split(",")])
+	except:
+		msg("Invalid format: %s" % rcpt_arg)
+		sys.exit(3)
+
+	try:
+		for i in tx_out.keys():
+			tx_out[i] = trim_exponent(Decimal(tx_out[i]))
+	except decimal.InvalidOperation:
+		msg("Decimal conversion error in suboption '%s:%s'" % (i,tx_out[i]))
+		sys.exit(3)
+
+	return tx_out

+ 63 - 2
mmgen/utils.py

@@ -22,6 +22,7 @@ utils.py:  Shared routines for the mmgen suite
 import sys
 import sys
 from mmgen.config import *
 from mmgen.config import *
 from binascii import hexlify,unhexlify
 from binascii import hexlify,unhexlify
+from mmgen.bitcoin import b58decode_pad
 
 
 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_r(s): sys.stderr.write(s)
@@ -628,12 +629,14 @@ def get_data_from_wallet(infile,opts):
 		sys.exit(9)
 		sys.exit(9)
 
 
 	res = {}
 	res = {}
-	from mmgen.bitcoin import b58decode_pad
 	for i,key in (4,"salt"),(5,"enc_seed"):
 	for i,key in (4,"salt"),(5,"enc_seed"):
 		l = lines[i].split()
 		l = lines[i].split()
 		val = "".join(l[1:])
 		val = "".join(l[1:])
 		_check_chksum_6(l[0], val, key, infile)
 		_check_chksum_6(l[0], val, key, infile)
 		res[key] = b58decode_pad(val)
 		res[key] = b58decode_pad(val)
+		if res[key] == False:
+			msg("Invalid b58 number: %s" % val)
+			sys.exit(9)
 
 
 	_check_chksum_6(lines[0], " ".join(lines[1:]), "Master", infile)
 	_check_chksum_6(lines[0], " ".join(lines[1:]), "Master", infile)
 
 
@@ -683,8 +686,11 @@ def get_seed_from_seed_data(words):
 	msg_r("Validating %s checksum..." % seed_ext)
 	msg_r("Validating %s checksum..." % seed_ext)
 
 
 	if compare_checksums(chk, "from seed", stored_chk, "from input"):
 	if compare_checksums(chk, "from seed", stored_chk, "from input"):
-		from mmgen.bitcoin import b58decode_pad
 		seed = b58decode_pad(seed_b58)
 		seed = b58decode_pad(seed_b58)
+		if seed == False:
+			msg("Invalid b58 number: %s" % val)
+			sys.exit(9)
+
 		msg("%s data produces seed ID: %s" % (seed_ext,make_chksum_8(seed)))
 		msg("%s data produces seed ID: %s" % (seed_ext,make_chksum_8(seed)))
 		return seed
 		return seed
 	else:
 	else:
@@ -778,5 +784,60 @@ def get_seed(infile,opts,no_wallet=False):
 	else:
 	else:
 		return get_seed_from_wallet(infile, opts)
 		return get_seed_from_wallet(infile, opts)
 
 
+def remove_blanks_comments(lines):
+	import re
+#	re.sub(pattern, repl, string, count=0, flags=0)
+	ret = []
+	for i in lines:
+		i = re.sub('#.*','',i,1)
+		i = re.sub('\s+$','',i)
+		if i: ret.append(i)
+
+	return ret
+
+def parse_addrs_file(f):
+	lines = get_lines_from_file(f,"address data")
+	lines = remove_blanks_comments(lines)
+
+	seed_id,obrace = lines[0].split()
+ 	cbrace = lines[-1]
+
+	if   obrace != '{':
+		msg("'%s': invalid first line" % lines[0])
+	elif cbrace != '}':
+		msg("'%s': invalid last line" % cbrace)
+	elif len(seed_id) != 8:
+		msg("'%s': invalid Seed ID" % seed_id)
+	else:
+		try:
+			unhexlify(seed_id)
+		except:
+			msg("'%s': invalid Seed ID" % seed_id)
+			sys.exit(3)
+		
+		ret = []
+		for i in lines[1:-1]:
+			d = i.split()
+
+			try: d[0] = int(d[0])
+			except:
+				msg("'%s': invalid address num. in line: %s" % (d[0],d))
+				sys.exit(3)
+
+			from mmgen.bitcoin import verify_addr
+			if not verify_addr(d[1]):
+				msg("'%s': invalid address" % d[1])
+				sys.exit(3)
+
+			ret.append(d)
+
+		return seed_id,ret
+
+	sys.exit(3)
+
+
+
+
+
 if __name__ == "__main__":
 if __name__ == "__main__":
 	print get_lines_from_file("/tmp/lines","test file")
 	print get_lines_from_file("/tmp/lines","test file")

+ 3 - 0
scripts/deinstall.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+set -x
+sudo rm -r /usr/local/bin/mmgen-* /usr/local/lib/python2.7/dist-packages/mmgen*

+ 15 - 3
setup.py

@@ -3,7 +3,7 @@ from distutils.core import setup
 
 
 setup(
 setup(
 		name         = 'mmgen',
 		name         = 'mmgen',
-		version      = '0.6.1',
+		version      = '0.6.2',
 		author       = 'Philemon',
 		author       = 'Philemon',
 		author_email = 'mmgen-py@yandex.com',
 		author_email = 'mmgen-py@yandex.com',
 		url          = 'https://github.com/mmgen/mmgen',
 		url          = 'https://github.com/mmgen/mmgen',
@@ -19,15 +19,27 @@ setup(
 			'mmgen.Opts',
 			'mmgen.Opts',
 			'mmgen.tx',
 			'mmgen.tx',
 			'mmgen.utils',
 			'mmgen.utils',
-			'mmgen.walletgen'
+			'mmgen.walletgen',
+			'mmgen.connection',
+			'mmgen.proxy',
+			'tests.addr',
+			'tests.bitcoin',
+			'tests.mn_electrum',
+			'tests.mnemonic',
+			'tests.mn_tirosh',
+			'tests.test',
+			'tests.utils',
+			'tests.walletgen'
 		],
 		],
 		data_files=[('/usr/local/bin', [
 		data_files=[('/usr/local/bin', [
 			'mmgen-addrgen',
 			'mmgen-addrgen',
+			'mmgen-addrimport',
 			'mmgen-keygen',
 			'mmgen-keygen',
 			'mmgen-passchg',
 			'mmgen-passchg',
 			'mmgen-walletchk',
 			'mmgen-walletchk',
 			'mmgen-walletgen',
 			'mmgen-walletgen',
 			'mmgen-txcreate',
 			'mmgen-txcreate',
-			'mmgen-txsign'
+			'mmgen-txsign',
+			'mmgen-txsend'
 		])]
 		])]
 	)
 	)

+ 5 - 0
tests/bitcoin.py

@@ -168,6 +168,10 @@ def hextosha256(s_in):
 	s_enc = sha256(unhexlify(s_in)).hexdigest()
 	s_enc = sha256(unhexlify(s_in)).hexdigest()
 	print "Encoded data:   %s" % s_enc
 	print "Encoded data:   %s" % s_enc
 
 
+def pubhextoaddr(s_in):
+	print "Entered data:   %s" % s_in
+	s_enc = pubhex2addr(s_in)
+	print "Encoded data:   %s" % s_enc
 
 
 tests = {
 tests = {
 	"keyconv_compare":          ['wif [str]','quiet [bool=False]'],
 	"keyconv_compare":          ['wif [str]','quiet [bool=False]'],
@@ -183,6 +187,7 @@ tests = {
 	"numtowif_rand":            ['quiet [bool=False]'],
 	"numtowif_rand":            ['quiet [bool=False]'],
 	"hextosha256":              ['hexnum [str]','quiet [bool=False]'],
 	"hextosha256":              ['hexnum [str]','quiet [bool=False]'],
 	"hextowiftopubkey":         ['hexnum [str]','quiet [bool=False]'],
 	"hextowiftopubkey":         ['hexnum [str]','quiet [bool=False]'],
+	"pubhextoaddr":             ['hexnum [str]','quiet [bool=False]'],
 }
 }