Version 0.7.4

Additions/improvements:

  mmgen-txsign     - sign multiple transactions in one operation

  mmgen-addrimport - skip rescanning block chain for new addresses

  mmgen-tool - new functions:

    Bitcoind operations:
    listaccounts - like 'bitcoind listaccounts' but shows MMGen wallet balances
                   too
    getbalance   - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
                   spendable/unspendable

    MMGen-specific operations:
    id8          - generate 8-character MMGen ID checksum for file (or stdin)
    id6          - generate 6-character MMGen ID checksum for file (or stdin)
This commit is contained in:
The MMGen Project 2014-07-21 10:20:15 +04:00
commit 242ff6acfa
8 changed files with 189 additions and 90 deletions

View file

@ -38,7 +38,8 @@ help_data = {
-v, --verbose Produce more verbose output
COMMANDS:{}
Type '{} <command> --help for usage information on a particular command
Type '{} <command> --help for usage information on a particular
command
""".format(command_help,prog_name)
}
@ -62,7 +63,7 @@ if cmd_args and cmd_args[0] == '--help':
tool_usage(prog_name, command)
sys.exit(0)
args = process_args(prog_name, command, cmd_args) if cmd_args else []
args = process_args(prog_name, command, cmd_args)
#print command + "(" + ", ".join(args) + ")"
eval(command + "(" + ", ".join(args) + ")")

View file

@ -77,7 +77,10 @@ qmsg("Signed transaction file '%s' is valid" % infile)
warn = "Once this transaction is sent, there's no taking it back!"
what = "broadcast this transaction to the network"
expect = "yes, i really want to do this".upper()
expect = "YES, I REALLY WANT TO DO THIS"
if g.quiet: warn,expect = "","YES"
confirm_or_exit(warn, what, expect)
msg("Sending transaction")

View file

@ -29,8 +29,8 @@ from mmgen.util import msg,qmsg
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
'desc': "Sign a Bitcoin transaction generated by mmgen-txcreate",
'usage': "[opts] <transaction file> [mmgen wallet/seed/words/brain file]...",
'desc': "Sign Bitcoin transactions generated by mmgen-txcreate",
'usage': "[opts] <transaction file>,.. [mmgen wallet/seed/words/brainwallet file]...",
'options': """
-h, --help Print this help message
-d, --outdir d Specify an alternate directory 'd' for output
@ -101,70 +101,77 @@ for i in infiles: check_infile(i)
c = connect_to_bitcoind()
tx_file = infiles.pop(0)
m = "" if 'tx_id' in opts else "transaction data"
tx_data = get_lines_from_file(tx_file,m)
seeds = {}
tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
infiles = list(set(infiles) - set(tx_files))
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
if not "info" in opts: do_license_msg(immed=True)
if 'tx_id' in opts:
msg(metadata[0])
sys.exit(0)
keys_from_file = get_lines_from_file(opts['keys_from_file'],"key data",
remove_comments=True) if 'keys_from_file' in opts else []
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
sys.exit(0)
for tx_file in tx_files:
m = "" if 'tx_id' in opts else "transaction data"
tx_data = get_lines_from_file(tx_file,m)
do_license_msg(immed=True)
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
if 'tx_id' in opts:
msg(metadata[0])
sys.exit(0)
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
sys.exit(0)
# Are inputs mmgen addresses?
mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
keys = get_lines_from_file(opts['keys_from_file'],"key data",remove_comments=True) \
if 'keys_from_file' in opts else []
keys = keys_from_file
if other_addrs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_addrs)
sys.exit(2)
if other_addrs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_addrs)
sys.exit(2)
if other_addrs and keys and not 'skip_key_preverify' in opts:
a = [i['address'] for i in other_addrs]
preverify_keys(a, keys)
if other_addrs and keys and not 'skip_key_preverify' in opts:
a = [i['address'] for i in other_addrs]
preverify_keys(a, keys)
opts['skip_key_preverify'] = True
seeds = {}
check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts)
check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts)
qmsg("Successfully opened transaction file '%s'" % tx_file)
if len(tx_files) > 1: msg("\nTransaction #%s:" % (tx_files.index(tx_file)+1))
qmsg("Successfully opened transaction file '%s'" % tx_file)
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
p = True if reply in "Vv" else False
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
p = True if reply in "Vv" else False
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
sig_data = [
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
for i in inputs_data]
sig_data = [
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
for i in inputs_data]
if mmgen_addrs:
ml = [i['account'].split()[0] for i in mmgen_addrs]
keys += get_keys_for_mmgen_addrs(ml,infiles,seeds,opts)
if mmgen_addrs:
ml = [i['account'].split()[0] for i in mmgen_addrs]
keys += get_keys_for_mmgen_addrs(ml,infiles,seeds,opts)
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_addrs:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
if sig_tx['complete']:
prompt = "OK\nSave signed transaction?"
if user_confirm(prompt,default_yes=True):
print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_addrs:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
if sig_tx['complete']:
prompt = "OK\nSave signed transaction?"
if user_confirm(prompt,default_yes=True):
print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
else:
msg("failed\nSome keys were missing. Transaction could not be signed.")
sys.exit(3)
msg("failed\nSome keys were missing. Transaction could not be signed.")
sys.exit(3)

View file

@ -37,6 +37,8 @@ incog_hex_ext = "mmincox"
seedfile_exts = wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext
rawtx_ext = "raw"
sigtx_ext = "sig"
addrfile_ext = "addrs"
keyfile_ext = "keys"

View file

@ -51,13 +51,21 @@ commands = {
"mn_rand192": ['wordlist [str="electrum"]'],
"mn_rand256": ['wordlist [str="electrum"]'],
"mn_stats": ['wordlist [str="electrum"]'],
"mn_printlist": ['wordlist [str="electrum"]']
"mn_printlist": ['wordlist [str="electrum"]'],
"id8": ['<infile> [str]'],
"id6": ['<infile> [str]'],
"listaccounts": ['minconf [int=1]'],
"getbalance": ['minconf [int=1]'],
}
command_help = """
General operations:
hexdump - encode binary data in formatted hexadecimal form
unhexdump - decode formatted hexadecimal data
File operations
hexdump - encode data into formatted hexadecimal form (file or stdin)
unhexdump - decode formatted hexadecimal data (file or stdin)
MMGen-specific operations
id8 - generate 8-character MMGen ID checksum for file (or stdin)
id6 - generate 6-character MMGen ID checksum for file (or stdin)
Bitcoin operations:
strtob58 - convert a string to base 58
@ -68,13 +76,20 @@ randwif - generate a random private key in WIF format
randpair - generate a random private key/address pair
wif2addr - generate a Bitcoin address from a key in WIF format
Mnemonic operations (choose "electrum" (default), "tirosh" or "all" wordlists):
Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
wordlists):
mn_rand128 - generate random 128-bit mnemonic
mn_rand192 - generate random 192-bit mnemonic
mn_rand256 - generate random 256-bit mnemonic
mn_stats - show stats for mnemonic wordlist
mn_printlist - print mnemonic wordlist
Bitcoind operations (bitcoind must be running):
listaccounts - like 'bitcoind listaccounts' but shows MMGen wallet balances
too
getbalance - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
spendable/unspendable
IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're
computed using a different algorithm and are NOT Electrum-compatible!
"""
@ -119,17 +134,23 @@ def process_args(prog_name, command, uargs):
ret = []
def normalize_arg(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"
return arg
for i in range(len(cargs_req)):
arg,arg_type = uargs_req[i], cargs_req[i][1]
if arg == "true" or arg == "false": arg = arg.capitalize()
arg_type = cargs_req[i][1]
arg = normalize_arg(uargs_req[i], arg_type)
if arg_type == "str":
ret.append('"%s"' % (arg))
elif test_type(arg_type, arg, "#"+str(i+1)):
ret.append('%s' % (arg))
for k in uargs_nam.keys():
arg,arg_type = uargs_nam[k], cargs_nam[k][0]
if arg == "true" or arg == "false": arg = arg.capitalize()
arg_type = cargs_nam[k][0]
arg = normalize_arg(uargs_nam[k], arg_type)
if arg_type == "str":
ret.append('%s="%s"' % (k, arg))
elif test_type(arg_type, arg, "'"+k+"'"):
@ -149,13 +170,13 @@ def print_convert_results(indata,enc,dec,no_recode=False):
msg("WARNING! Recoded number doesn't match input stringwise!")
def hexdump(infile, cols=8, line_nums=True):
data = get_data_from_file(infile)
o = pretty_hexdump(data, 2, cols, line_nums)
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
o = pretty_hexdump(d, 2, cols, line_nums)
print o
def unhexdump(infile):
data = get_data_from_file(infile)
o = decode_pretty_hexdump(data)
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
o = decode_pretty_hexdump(d)
sys.stdout.write(o)
def strtob58(s):
@ -242,3 +263,61 @@ def mn_stats(wordlist="electrum"):
def mn_printlist(wordlist="electrum"):
l = get_wordlist(wordlist)
print "%s" % l.strip()
def id8(infile):
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
print make_chksum_8(d)
def id6(infile):
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
print make_chksum_6(d)
def listaccounts(minconf=1):
from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
def s_mmgen(i):
ma = i[0].split(" ")[0] if " " in i[0] else i[0]
if is_mmgen_addr(ma):
mmid,idx = ma.split(":")
return mmid + ":" + ("%04i" % int(idx))
else:
return "G"+i[0]
c = connect_to_bitcoind()
data = [(a,c.getbalance(a,minconf)) for a in c.listaccounts()]
data.sort(key=s_mmgen)
col_w = max([len(d[0]) for d in data])
fs = "%-"+str(col_w)+"s %s"
print fs % ("ACCOUNT","BALANCE")
totals = {}
for d in data:
ma = d[0].split(" ")[0] if " " in d[0] else d[0]
if is_mmgen_addr(ma):
mmid = ma.split(":")[0]
if mmid not in totals: totals[mmid] = 0
totals[mmid] += d[1]
print fs % (
d[0] if d[0] else 'TOTAL:',
trim_exponent(d[1])
)
print "\nMMGEN WALLET BALANCES"
for k in totals.keys():
print "%s: %s" % (k, trim_exponent(totals[k]))
def getbalance(minconf=1):
from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
c = connect_to_bitcoind()
data = c.listunspent(0)
o = [0,0,0,0,0,0] # su,sb,sc, uu,ub,uc
for d in data:
j = 0 if d.spendable else 3
if d.confirmations == 0: o[j] += d.amount
k = 1 if d.confirmations < minconf else 2
o[j+k] += d.amount
fs = "{}:\n {:<12} unconfirmed\n {:<12} <{M} {C}\n {:<12} >={M} {C}"
for lbl,n in ("Spendable",0),("Unspendable",3):
if sum(o[n:3+n]) == 0:
print "{}: {}".format(lbl,"NONE")
else:
print fs.format(lbl,o[n+0],o[n+1],o[n+2],M=minconf,C="confirmations")

View file

@ -141,7 +141,7 @@ def get_bitcoind_cfg_options(cfg_keys):
def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
tx_id = make_chksum_6(unhexlify(tx)).upper()
outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
outfile = "tx_%s[%s].%s" % (tx_id,send_amt,g.rawtx_ext)
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
@ -156,7 +156,7 @@ def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
def print_signed_tx_to_file(tx,sig_tx,metadata,opts):
tx_id = make_chksum_6(unhexlify(tx)).upper()
outfile = "tx_{}[{}].sig".format(*metadata[:2])
outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
data = "%s\n%s\n" % (" ".join(metadata),sig_tx)
@ -174,16 +174,17 @@ def print_sent_tx_to_file(tx,metadata,opts):
def format_unspent_outputs_for_printing(out,sort_info,total):
pfs = " %-4s %-67s %-34s %-12s %-13s %-10s %s"
pfs = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
"Amount (BTC)","Age (days)", "Comment")]
"Amount (BTC)","Confirms","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)
s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,i.mmid,i.amt,i.days,i.label)
s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,
i.mmid,i.amt,i.confirmations,i.days,i.label)
pout.append(s.rstrip())
return \
@ -200,14 +201,16 @@ def sort_and_view(unspent):
def s_age(i): return i.confirmations
def s_mmgen(i): return i.account
sort,group,show_mmaddr,reverse = "",False,False,False
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]))
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: [g]roup, show [m]mgen addr, r[e]draw screen
Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
""".strip()
prompt = \
"('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
@ -230,8 +233,8 @@ Display options: [g]roup, show [m]mgen addr, r[e]draw screen
addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
tx_w = max(11,min(64, cols-addr_w-32))
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)")
a = "Age(d)" if show_days else "Confirms"
table_hdr = fs % ("Num","TX id Vout","","Address", "Amount (BTC)",a)
unsp = deepcopy(unspent)
for i in unsp: i.skip = ""
@ -277,12 +280,13 @@ Display options: [g]roup, show [m]mgen addr, r[e]draw screen
out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
for n,i in enumerate(unsp):
out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
d = i.days if show_days else i.confirmations
out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,d))
msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg)
print_to_file_msg = ""
immed_chars = "atdAMrgmeqpvw"
immed_chars = "atDdAMrgmeqpvw"
skip_prompt = False
while True:
@ -290,6 +294,7 @@ Display options: [g]roup, show [m]mgen addr, r[e]draw screen
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 = False if show_days else True
elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
elif reply == 'A': unspent.sort(key=s_age); sort = "age"
elif reply == 'M':

View file

@ -42,7 +42,9 @@ def vmsg_r(s):
def bail(): sys.exit(9)
def get_extension(f): return f.split(".")[-1]
def get_extension(f):
try: return f.split(".")[-1]
except: return ""
def my_raw_input(prompt,echo=True,allowed_chars=""):
try:

View file

@ -3,7 +3,7 @@ from distutils.core import setup
setup(
name = 'mmgen',
version = '0.7.3b',
version = '0.7.4',
author = 'Philemon',
author_email = 'mmgen-py@yandex.com',
url = 'https://github.com/mmgen/mmgen',