* better options handling

* code cleanups
* new commands in 'mmgen-tool'
* tx view in 'mmgen-txsend'
This commit is contained in:
The MMGen Project 2014-07-27 00:33:45 +04:00
commit 38e0a954d0
19 changed files with 579 additions and 661 deletions

View file

@ -41,4 +41,3 @@ mmgen/tests/mnemonic.py
mmgen/tests/test.py
mmgen/tests/util.py
mmgen/tests/walletgen.py
test/test.py

View file

@ -30,51 +30,51 @@ from mmgen.license import *
from mmgen.util 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 = ("","","")
gen_what = "addresses" if sys.argv[0].split("-")[-1] == "addrgen" else "keys"
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
'desc': """Generate a list or range of {} from a {} wallet,
mnemonic, seed or password""".format(gen_what,g.proj_name),
'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
mnemonic, seed or password""".format(gen_what,g=g),
'usage':"[opts] [infile] <address list>",
'options': """
-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
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
-H, --show-hash-presets Show information on available hash presets
-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, --passwd-file f Get passphrase from file 'f'
-l, --seed-len= N Length of seed. Options: {seed_lens}
(default: {g.seed_len})
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
hashing password (default: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-S, --stdout Print {W} to stdout
-S, --stdout Print {what} to stdout
-v, --verbose Produce more verbose output{}
-b, --from-brain l,p Generate {W} from a user-created password,
-b, --from-brain= l,p Generate {what} from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-g, --from-incog Generate {W} from an incognito wallet
-X, --from-incog-hex Generate {W} from an incognito hexadecimal wallet
-G, --from-incog-hidden f,o,l Generate {W} from incognito data in file
-g, --from-incog Generate {what} from an incognito wallet
-X, --from-incog-hex Generate {what} from incognito hexadecimal wallet
-G, --from-incog-hidden= f,o,l Generate {what} from incognito data in file
'f' at offset 'o', with seed length of 'l'
-m, --from-mnemonic Generate {W} from an electrum-like mnemonic
-s, --from-seed Generate {W} from a seed in .{S} format
-m, --from-mnemonic Generate {what} from an electrum-like mnemonic
-s, --from-seed Generate {what} from a seed in .{g.seed_ext} format
""".format(
*(
("\n-A, --no-addresses Print only secret keys, no addresses",
"\n-x, --b16 Print secret keys in hexadecimal too")
if gen_what == "keys" else ("","")),
seed_lens=", ".join([str(i) for i in g.seed_lens]),
what=gen_what, g=g
),
'notes': """
Addresses are given in a comma-separated list. Hyphen-separated
ranges are also allowed.{}
Addresses are given in a comma-separated list. Hyphen-separated ranges are
also allowed.{}
If available, the external 'keyconv' program will be used for address
generation.
@ -96,36 +96,14 @@ 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 g.seed_lens]),
g.seed_len,
g.hash_preset,
extra_help_data[1],
extra_help_data[2],
W=gen_what,
S=g.seed_ext
)
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("\n\nBy default, both addresses and secret keys are generated."
if gen_what == "keys" else "")
}
short_opts = ["h","A","d:","e",
"H","K","l:","p:",
"P:","q","S","v","x","b:",
"g","X","G:","m","s"]
long_opts = ["help","no_addresses","outdir=","echo_passphrase",
"show_hash_presets","no_keyconv","seed_len=","hash_preset=",
"passwd_file=","quiet","stdout","verbose","b16","from_brain=",
"from_incog","from_incog_hex","from_incog_hidden=",
"from_mnemonic","from_seed"]
if invoked_as == "addrgen":
for i in "A","x": short_opts.remove(i)
for i in "no_addresses","b16": long_opts.remove(i)
opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'show_hash_presets' in opts: show_hash_presets()
if 'verbose' in opts: g.verbose = True
@ -135,8 +113,6 @@ if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['gen_what'] = gen_what
check_opts(opts,long_opts)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1 and (
@ -158,12 +134,12 @@ if not addr_list: sys.exit(2)
do_license_msg()
# Interact with user:
if invoked_as == "keygen" and not g.quiet:
if gen_what == "keys" and not g.quiet:
confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
# Generate data:
if invoked_as == "addrgen":
if gen_what == "addresses":
opts['print_addresses_only'] = True
else:
if not 'no_addresses' in opts: opts['print_secret'] = True
@ -172,11 +148,16 @@ seed = get_seed_retry(infile,opts)
seed_id = make_chksum_8(seed)
addr_data = generate_addrs(seed, addr_list, opts)
addr_data_str = format_addr_data(addr_data, seed_id, opts)
addr_data_chksum = make_chksum_8(
" ".join(["{} {}".format(a['num'],a['addr']) for a in addr_data]),
sep=True
)
addr_data_str = format_addr_data(
addr_data, addr_data_chksum, seed_id, addr_list, opts)
# Output data:
if 'stdout' in opts:
if invoked_as == "keygen" and not g.quiet:
if gen_what == "keys" and not g.quiet:
confirm = True
else: confirm = False
write_to_stdout(addr_data_str,"secret keys",confirm)
@ -184,3 +165,8 @@ 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, addr_list, opts)
msg("""
Checksum for address data {}[{}]: {}
Remember this checksum or save it to a secure location
""".format(seed_id, fmt_addr_list(addr_list), addr_data_chksum).strip())

View file

@ -33,25 +33,21 @@ help_data = {
'usage':"[opts] [mmgen address file]",
'options': """
-h, --help Print this help message
-l, --addrlist f Import the non-mmgen Bitcoin addresses listed in file 'f'
-l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f'
-q, --quiet Suppress warnings
-r, --rescan Rescan the blockchain. Required if address to import is
on the blockchain and has a balance. Rescanning is slow.
-q, --quiet Suppress warnings
"""
}
short_opts = "hl:qr"
long_opts = "help", "addrlist=", "quiet", "rescan"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
if 'quiet' in opts: g.quiet = True
if len(cmd_args) != 1 and not 'addrlist' in opts:
msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
sys.exit(1)
check_opts(opts,long_opts)
if cmd_args:
check_infile(cmd_args[0])
seed_id,addr_data = parse_addrs_file(cmd_args[0])
@ -59,9 +55,9 @@ else:
seed_id,addr_data = "",[]
if 'addrlist' in opts:
l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
remove_comments=True)
addr_data += [(None,i) for i in l]
addr_data += [(None,l) for l in lines]
from mmgen.bitcoin import verify_addr
qmsg_r("Validating addresses...")

View file

@ -26,37 +26,34 @@ from mmgen.util import *
import mmgen.config as g
help_data = {
'desc': """Change the passphrase, hash preset or label of a {}
'desc': """Change the passphrase, hash preset or label of an {}
deterministic wallet""".format(g.proj_name),
'usage': "[opts] [filename]",
'options': """
-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
-H, --show-hash-presets Show information on available hash presets
-k, --keep-old-passphrase Keep old passphrase (use when changing hash
strength or label only)
-L, --label l Change the wallet's label to 'l'
-p, --hash-preset p Change scrypt.hash() parameters to preset 'p'
(default: '{}')
-P, --passwd-file f Get new passphrase from file 'f'
-L, --label= l Change the wallet's label to 'l'
-p, --hash-preset= p Change scrypt.hash() parameters to preset 'p'
(default: '{g.hash_preset}')
-P, --passwd-file= f Get new passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-v, --verbose Produce more verbose output
""".format(g=g),
'notes': """
NOTE: The key ID will change if either the passphrase or hash preset
are changed
""".format(g.hash_preset)
NOTE: The key ID will change if either the passphrase or hash preset are
changed
"""
}
short_opts = "hd:HkL:p:P:qv"
long_opts = "help","outdir=","show_hash_presets","keep_old_passphrase",\
"label=","hash_preset=","passwd_file=","quiet","verbose"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
check_opts(opts,long_opts)
if 'show_hash_presets' in opts: show_hash_presets()
if len(cmd_args) != 1:

View file

@ -75,26 +75,21 @@ help_data = {
'usage': "[opts] <bitcoind wallet file>",
'options': """
-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
-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'
-K, --keysforaddrs= f Dump private keys for addresses listed in file 'f'
-P, --passwd-file= f Get passphrase from file 'f'
-S, --stdout Dump to stdout rather than file
"""
}
short_opts = "hd:ejkaK:P:S"
long_opts = "help","outdir=","echo_passphrase","json","keys","addrs",\
"keysforaddrs=","passwd_file=","stdout"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
check_opts(opts,long_opts)
from mmgen.util import check_infile
if len(cmd_args) == 1:
from mmgen.util import check_infile
check_infile(cmd_args[0])
else:
usage(help_data)

View file

@ -36,6 +36,8 @@ help_data = {
-h, --help Print this help message
-q, --quiet Produce quieter output
-v, --verbose Produce more verbose output
""",
'notes': """
COMMANDS:{}
Type '{} <command> --help for usage information on a particular
@ -43,11 +45,9 @@ command
""".format(command_help,prog_name)
}
short_opts = "hqv"
long_opts = "help","quiet","verbose"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if len(cmd_args) < 1:

View file

@ -37,34 +37,32 @@ help_data = {
'usage': "[opts] <addr,amt> ... [change addr] [addr file] ...",
'options': """
-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
-e, --echo-passphrase Print passphrase to screen when typing it
-f, --tx-fee= f Transaction fee (default: {g.tx_fee} BTC)
-i, --info Display unspent outputs and exit
-q, --quiet Suppress warnings; overwrite files without
prompting
-f, --tx-fee f Transaction fee (default: %s BTC)
""".format(g=g),
'notes': """
Transaction inputs are chosen from a list of the user's unpent outputs
via an interactive menu.
Ages of transactions are approximate based on an average block creation
interval of %s minutes.
interval of {g.mins_per_block} minutes.
Addresses on the command line can be Bitcoin addresses or MMGen addresses
of the form <seed ID>:<number>.
To send all inputs (minus TX fee) to a single output, specify one address
with no amount on the command line.
""" % (Decimal(g.tx_fee),g.mins_per_block)
""".format(g=g)
}
short_opts = "hd:eiqf:"
long_opts = "help","outdir=","echo_passphrase","info","quiet","tx_fee="
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
check_opts(opts,long_opts)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
@ -74,31 +72,39 @@ if not 'info' in opts:
tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
for a in [i for i in cmd_args if match_ext(i,g.addrfile_ext)]:
if match_ext(a,g.addrfile_ext):
check_infile(a)
addr_data.append(parse_addrs_file(a))
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
cmd_args = set(cmd_args) - set(addrfiles)
def mm_addr2btc_addr(c,mmadr,acct_data,addr_data,b2m_map):
btcaddr,label = mmgen_addr_to_walletd(c,mmadr,acct_data)
for a in addrfiles:
check_infile(a)
addr_data.append(parse_addrs_file(a))
def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map):
# assume mmaddr has already been checked
btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data)
if not btcaddr:
btcaddr,label = mmgen_addr_to_addr_data(mmadr,addr_data)
b2m_map[btcaddr] = mmadr,label
if addr_data:
btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data)
else:
msg(txmsg['addrfile_no_data_msg'] % mmaddr)
sys.exit(2)
b2m_map[btcaddr] = mmaddr,label
return btcaddr
for a in [i for i in cmd_args if not match_ext(i,g.addrfile_ext)]:
for a in cmd_args:
if "," in a:
a1,a2 = a.split(",")
if is_btc_addr(a1):
btcaddr = a1
elif is_mmgen_addr(a1):
btcaddr = mm_addr2btc_addr(c,a1,acct_data,addr_data,b2m_map)
btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map)
else:
msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
sys.exit(2)
if is_btc_amt(a2):
tx_out[btcaddr] = check_btc_amt(a2)
tx_out[btcaddr] = normalize_btc_amt(a2)
else:
msg("%s: invalid amount in argument '%s'" % (a2,a))
sys.exit(2)
@ -108,7 +114,7 @@ if not 'info' in opts:
(change_addr, a))
sys.exit(2)
change_addr = a if is_btc_addr(a) else \
mm_addr2btc_addr(c,a,acct_data,addr_data,b2m_map)
mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map)
tx_out[change_addr] = 0
else:
msg("%s: unrecognized argument" % a)
@ -119,7 +125,7 @@ if not 'info' in opts:
sys.exit(2)
tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
tx_fee = check_btc_amt(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)
@ -128,21 +134,15 @@ if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if not 'info' in opts: do_license_msg(immed=True)
# Begin test
# import mmgen.rpc
# us = eval(get_data_from_file("listunspent.json"))
# End test
#write_to_file("bogus_unspent.json", repr(us)); sys.exit()
if g.bogus_wallet_data:
import mmgen.rpc
us = eval(get_data_from_file("bogus_unspent.json"))
else:
us = c.listunspent()
us = c.listunspent()
if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
if not us:
msg("""
No spendable outputs found! Import addresses with balances into your
watch-only wallet using 'mmgen-addrimport' and then re-run this program.""")
sys.exit(2)
# write_to_file("listunspent.json",repr(us))
# sys.exit()
unspent = sort_and_view(us)
total = trim_exponent(sum([i.amount for i in unspent]))
@ -209,6 +209,6 @@ if reply and reply in "YyVv":
prompt = "Save transaction?"
if user_confirm(prompt,default_yes=True):
print_tx_to_file(tx_hex,sel_unspent,send_amt or change,b2m_map,opts)
write_tx_to_file(tx_hex,sel_unspent,send_amt or change,b2m_map,opts)
else:
msg("Transaction not saved")

View file

@ -35,46 +35,37 @@ help_data = {
'usage': "[opts] <signed transaction file>",
'options': """
-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
-q, --quiet Suppress warnings; overwrite files without prompting
"""
}
short_opts = "hd:q"
long_opts = "help","outdir=","quiet"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
check_opts(opts,long_opts)
if len(cmd_args) == 1:
infile = cmd_args[0]
check_infile(infile)
infile = cmd_args[0]; check_infile(infile)
else: usage(help_data)
# Begin execution
try:
metadata,tx_sig = get_lines_from_file(infile,"signed transaction")
except:
msg("Invalid signed transaction file")
sys.exit(3)
metadata = metadata.split()
if len(metadata) != 3:
msg("Invalid file header")
sys.exit(3)
from binascii import unhexlify
try: unhexlify(tx_sig)
except:
msg("Invalid signed transaction data")
sys.exit(3)
do_license_msg()
tx_data = get_lines_from_file(infile,"signed transaction data")
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile)
qmsg("Signed transaction file '%s' is valid" % infile)
c = connect_to_bitcoind()
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)
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"
@ -85,14 +76,12 @@ confirm_or_exit(warn, what, expect)
msg("Sending transaction")
c = connect_to_bitcoind()
try:
tx = c.sendrawtransaction(tx_sig)
tx_id = c.sendrawtransaction(tx_hex)
except:
msg("Unable to send transaction")
sys.exit(3)
msg("Transaction sent: %s" % tx)
msg("Transaction sent: %s" % tx_id)
print_sent_tx_to_file(tx,metadata,opts)
write_sent_tx_num_to_file(tx_id,metadata,opts)

View file

@ -33,26 +33,27 @@ help_data = {
'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
-d, --outdir= d Specify an alternate directory 'd' for output
-e, --echo-passphrase Print passphrase to screen when typing it
-i, --info Display information about the transaction and exit
-I, --tx-id Display transaction ID and exit
-k, --keys-from-file k Provide additional key data from file 'k'
-P, --passwd-file f Get passphrase from file 'f'
-k, --keys-from-file= k Provide additional key data from file 'k'
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-V, --skip-key-preverify Skip optional key pre-verification step
-b, --from-brain l,p Generate keys from a user-created password,
-b, --from-brain= l,p Generate keys from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p'
-w, --use-wallet-dat Get keys from a running bitcoind
-g, --from-incog Generate keys from an incognito wallet
-X, --from-incog-hex Generate keys from an incognito hexadecimal wallet
-G, --from-incog-hidden f,o,l Generate keys from incognito data in file
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
'f' at offset 'o', with seed length of 'l'
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
-s, --from-seed Generate keys from a seed in .{} format
-s, --from-seed Generate keys from a seed in .{g.seed_ext} format
""".format(g=g),
'notes': """
Transactions with either mmgen or non-mmgen input addresses may be signed.
For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
@ -74,34 +75,25 @@ mappings are verified. Therefore, seed material for these addresses must
be supplied on the command line.
Seed data supplied in files must have the following extensions:
wallet: '.{}'
seed: '.{}'
mnemonic: '.{}'
brainwallet: '.{}'
""".format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext)
wallet: '.{g.wallet_ext}'
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_ext}'
brainwallet: '.{g.brain_ext}'
""".format(g=g)
}
short_opts = "hd:eiIk:P:qVb:wgXG:ms"
long_opts = "help","outdir=","echo_passphrase","info","tx_id",\
"keys_from_file=","passwd_file=","quiet","skip_key_preverify",\
"from_brain=","use_wallet_dat",\
"from_incog","from_incog_hex","from_incog_hidden=",\
"from_mnemonic","from_seed"
opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
opts,infiles = parse_opts(sys.argv,help_data)
if "quiet" in opts: g.quiet = True
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
check_opts(opts,long_opts)
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
c = connect_to_bitcoind()
seeds = {}
saved_seeds = {}
tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
infiles = list(set(infiles) - set(tx_files))
@ -139,9 +131,10 @@ for tx_file in tx_files:
preverify_keys(a, keys)
opts['skip_key_preverify'] = True
check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts)
check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,saved_seeds,opts)
if len(tx_files) > 1: msg("\nTransaction #%s:" % (tx_files.index(tx_file)+1))
if len(tx_files) > 1:
msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files)))
qmsg("Successfully opened transaction file '%s'" % tx_file)
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
@ -156,7 +149,7 @@ for tx_file in tx_files:
if mmgen_addrs:
ml = [i['account'].split()[0] for i in mmgen_addrs]
keys += get_keys_for_mmgen_addrs(ml,infiles,seeds,opts)
keys += get_keys_for_mmgen_addrs(ml,infiles,saved_seeds,opts)
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
@ -171,7 +164,7 @@ for tx_file in tx_files:
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)
write_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,inputs_data,b2m_map,opts)
else:
msg("failed\nSome keys were missing. Transaction could not be signed.")
sys.exit(3)

View file

@ -27,42 +27,34 @@ from mmgen.util import *
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
'desc': """Check integrity of a %s deterministic wallet, display
its information and export seed and mnemonic data."""\
% g.proj_name,
'desc': """Check integrity of an {} deterministic wallet, display
its information, and export seed and mnemonic data.
""".format(g.proj_name),
'usage': "[opts] [filename]",
'options': """
-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
-e, --echo-passphrase Print passphrase to screen when typing it
-P, --passwd-file f Get passphrase from file 'f'
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-S, --stdout Print seed or mnemonic data to standard output
-v, --verbose Produce more verbose output
-g, --export-incog Export wallet to incognito format
-X, --export-incog-hex Export wallet to incognito hexadecimal format
-G, --export-incog-hidden f,o Hide incognito data in existing file 'f'
-G, --export-incog-hidden= f,o Hide incognito data in existing file 'f'
at offset 'o' (comma-separated)
-m, --export-mnemonic Export the wallet's mnemonic to file
-s, --export-seed Export the wallet's seed to file
"""
}
short_opts = "hd:eP:qSvgXG:ms"
long_opts = "help","outdir=","echo_passphrase","passwd_file=","quiet",\
"stdout","verbose",\
"export_incog","export_incog_hex","export_incog_hidden=",\
"export_mnemonic","export_seed"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'export_incog_hidden' in opts or 'export_incog_hex' in opts:
opts['export_incog'] = True
# Argument sanity checks and processing:
check_opts(opts,long_opts)
if len(cmd_args) != 1: usage(help_data)
check_infile(cmd_args[0])

View file

@ -31,35 +31,37 @@ prog_name = sys.argv[0].split("/")[-1]
help_data = {
'prog_name': prog_name,
'desc': "Generate a {} deterministic wallet".format(g.proj_name),
'desc': "Generate an {} deterministic wallet".format(g.proj_name),
'usage': "[opts] [infile]",
'options': """
-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
-e, --echo-passphrase Print passphrase to screen when typing it
-H, --show-hash-presets Show information on available hash presets
-l, --seed-len n Create seed of length 'n'. Options: {}
(default: {})
-L, --label l Label to identify this wallet (32 chars max.
-l, --seed-len= n Create seed of length 'n'. Options: {seed_lens}
(default: {g.seed_len})
-L, --label= l Label to identify this wallet (32 chars max.
Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
-p, --hash-preset p Use scrypt.hash() parameters from preset 'p'
(default: '{}')
-P, --passwd-file f Get passphrase from file 'f'
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p'
(default: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Produce quieter output; overwrite files without
prompting
-u, --usr-randlen n Get 'n' characters of randomness from the user
(default: {})
-u, --usr-randlen= n Get 'n' characters of randomness from the user
(default: {g.usr_randlen})
-v, --verbose Produce more verbose output
-b, --from-brain l,p Generate wallet from a user-created passphrase,
-b, --from-brain= l,p Generate wallet from a user-created passphrase,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-g, --from-incog Generate wallet from an incognito-format wallet
-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic
-s, --from-seed Generate wallet from a seed in .{S} format
-s, --from-seed Generate wallet from a seed in .{g.seed_ext} format
""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g),
'notes': """
By default (i.e. when invoked without any of the '--from-<what>' options),
{} generates a wallet based on a random seed.
{prog_name} generates a wallet based on a random seed.
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.
@ -84,28 +86,15 @@ the '--seed-len' option.
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(
",".join([str(i) for i in g.seed_lens]),
g.seed_len,
g.hash_preset,
g.usr_randlen,
prog_name,
S=g.seed_ext,
)
""".format(prog_name=prog_name)
}
short_opts = "hd:eHl:L:p:P:qu:vb:gms"
long_opts = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=","verbose",\
"from_brain=","from_incog","from_mnemonic","from_seed"
opts,cmd_args = parse_opts(sys.argv,help_data)
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'show_hash_presets' in opts: show_hash_presets()
check_opts(opts,long_opts)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1:

View file

@ -23,19 +23,29 @@ def usage(hd):
print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
sys.exit(2)
def print_version_info(progname):
print """
'{}' version {g.version}. Part of the {g.proj_name} suite.
Copyright (C) {g.Cdates} by {g.author} {g.email}.
""".format(progname, g=g).strip()
def print_help(progname,help_data):
pn_len = str(len(progname)+2)
print (" %-"+pn_len+"s %s") % (progname.upper()+":", help_data['desc'])
print (" %-"+pn_len+"s %s %s") % ("USAGE:", progname, help_data['usage'])
print (" %-"+pn_len+"s %s") % (progname.upper()+":", help_data['desc'].strip())
print (" %-"+pn_len+"s %s %s")%("USAGE:", progname, help_data['usage'].strip())
sep = "\n "
print " OPTIONS:"+sep+"%s" % sep.join(help_data['options'].strip().split("\n"))
if "notes" in help_data:
print " %s" % "\n ".join(help_data['notes'][1:-1].split("\n"))
def process_opts(argv,help_data,short_opts,long_opts):
progname = argv[0].split("/")[-1]
if len(argv) == 2 and argv[1] == '--version': # MMGen only!
print_version_info(progname); sys.exit()
if g.debug:
print "Short opts: %s" % repr(short_opts)
print "Long opts: %s" % repr(long_opts)
@ -69,6 +79,26 @@ def process_opts(argv,help_data,short_opts,long_opts):
return opts,args
def parse_opts(argv,help_data):
lines = help_data['options'].strip().split("\n")
import re
pat = r"^-([a-zA-Z0-9]), --([a-zA-Z0-9-]{1,64})(=*) (.*)"
opt_data = [m.groups() for m in [re.match(pat,l) for l in lines] if m]
short_opts = "".join([d[0]+(":" if d[2] else "") for d in opt_data if d])
long_opts = [d[1].replace("-","_")+d[2] for d in opt_data if d]
help_data['options'] = "\n".join(
["-{0}, --{1}{w} {3}".format(w=" " if m.group(3) else "", *m.groups())
if m else k for m,k in [(re.match(pat,l),l) for l in lines]]
)
opts,infiles = process_opts(argv,help_data,short_opts,long_opts)
if not check_opts(opts,long_opts): sys.exit(1) # MMGen only!
return opts,infiles
def show_opts_and_cmd_args(opts,cmd_args):
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
@ -85,51 +115,41 @@ def check_opts(opts,long_opts):
if i+"=" in long_opts:
set_if_unset_and_typeconvert(opts,i)
for opt in opts.keys():
for opt,val in opts.items():
val = opts[opt]
what = "parameter for '--%s' option" % opt.replace("_","-")
# Check for file existence and readability
for i in 'keys_from_file','addrlist','passwd_file','keysforaddrs':
if opt == i:
check_infile(val)
return
if opt in ('keys_from_file','addrlist','passwd_file','keysforaddrs'):
check_infile(val)
return True
if opt == 'outdir':
what = "output directory"
import re, os, stat
# TODO Non-portable:
d = re.sub(r'/*$','', val)
opts[opt] = d
import os
if os.path.isdir(val):
if os.access(val, os.W_OK|os.X_OK):
opts[opt] = os.path.normpath(val)
else:
msg("Requested %s '%s' is unwritable by you" % (what,val))
return False
else:
msg("Requested %s '%s' doen not exist" % (what,val))
return False
try: mode = os.stat(d).st_mode
except:
msg("Unable to stat requested %s '%s'" % (what,d))
sys.exit(1)
if not stat.S_ISDIR(mode):
msg("Requested %s '%s' is not a directory" % (what,d))
sys.exit(1)
if not os.access(d, os.W_OK|os.X_OK):
msg("Requested %s '%s' is unwritable by you" % (what,d))
sys.exit(1)
elif opt == 'label':
label = val.strip()
opts[opt] = label
if len(label) > g.max_wallet_label_len:
if len(val) > g.max_wallet_label_len:
msg("Label must be %s characters or less" %
g.max_wallet_label_len)
sys.exit(1)
return False
for ch in list(label):
if ch not in g.wallet_label_symbols:
msg("""
"%s": illegal character in label. Only ASCII characters are permitted.
""".strip() % ch)
sys.exit(1)
for ch in list(val):
chs = g.wallet_label_symbols
if ch not in chs:
msg("'%s': ERROR: label contains an illegal symbol" % val)
msg("The following symbols are permitted:\n%s" % "".join(chs))
return False
elif opt == 'export_incog_hidden' or opt == 'from_incog_hidden':
try:
if opt == 'export_incog_hidden':
@ -138,87 +158,89 @@ def check_opts(opts,long_opts):
outfile,offset,seed_len = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(1)
return False
try:
o = int(offset)
except:
msg("'%s': invalid 'o' %s (not an integer)" % (offset,what))
sys.exit(1)
return False
if o < 0:
msg("'%s': invalid 'o' %s (less than zero)" % (offset,what))
sys.exit(1)
return False
if opt == 'from_incog_hidden':
try:
sl = int(seed_len)
except:
msg("'%s': invalid 'l' %s (not an integer)" % (sl,what))
sys.exit(1)
return False
if sl not in g.seed_lens:
msg("'%s': invalid 'l' %s (valid choices: %s)" %
(sl,what," ".join(str(i) for i in g.seed_lens)))
sys.exit(1)
return False
import os, stat
try: mode = os.stat(outfile).st_mode
except:
msg("Unable to stat requested %s '%s'" % (what,outfile))
sys.exit(1)
return False
if not (stat.S_ISREG(mode) or stat.S_ISBLK(mode)):
msg("Requested %s '%s' is not a file or block device" %
(what,outfile))
sys.exit(1)
return False
ac,m = (os.W_OK,"writ") \
if "export_incog_hidden" in opts else (os.R_OK,"read")
if not os.access(outfile, ac):
msg("Requested %s '%s' is un%sable by you" % (what,outfile,m))
sys.exit(1)
return False
elif opt == 'from_brain':
try:
l,p = val.split(",")
except:
msg("'%s': invalid %s" % (val,what))
sys.exit(2)
return False
try:
int(l)
except:
msg("'%s': invalid 'l' %s (not an integer)" % (l,what))
sys.exit(1)
return False
if int(l) not in g.seed_lens:
msg("'%s': invalid 'l' %s. Options: %s" %
(l, what, ", ".join([str(i) for i in g.seed_lens])))
sys.exit(1)
return False
if p not in g.hash_presets:
hps = ", ".join([i for i in sorted(g.hash_presets.keys())])
msg("'%s': invalid 'p' %s. Options: %s" % (p, what, hps))
sys.exit(1)
return False
elif opt == 'seed_len':
if val not in g.seed_lens:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join([str(i) for i in g.seed_lens])))
sys.exit(2)
return False
elif opt == 'hash_preset':
if val not in g.hash_presets:
msg("'%s': invalid %s. Options: %s"
% (val,what,", ".join(sorted(g.hash_presets.keys()))))
sys.exit(2)
return False
elif opt == 'usr_randlen':
if val > g.max_randlen or val < g.min_randlen:
msg("'%s': invalid %s (must be >= %s and <= %s)"
% (val,what,g.min_randlen,g.max_randlen))
sys.exit(2)
return False
else:
if g.debug: print "check_opts(): No test for opt '%s'" % opt
return True
def set_if_unset_and_typeconvert(opts,opt):

View file

@ -21,12 +21,25 @@ addr.py: Address generation/display routines for mmgen suite
import sys
from hashlib import sha256, sha512
from hashlib import new as hashlib_new
from binascii import hexlify, unhexlify
from mmgen.bitcoin import numtowif
from mmgen.util import msg,qmsg,qmsg_r
import mmgen.config as g
addrmsgs = {
'addrfile_header': """
# MMGen address file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {}.
# A text label of {} characters or less may be added to the right of each
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain any printable ASCII symbol.
""".strip().format(g.proj_name,g.max_addr_label_len)
}
def test_for_keyconv():
"""
Test for the presence of 'keyconv' utility on system
@ -112,22 +125,10 @@ def generate_keys(seed, addrnums):
return generate_addrs(seed, addrnums, o)
def format_addr_data(addrlist, seed_chksum, opts):
"""
print_addresses(addrs, opts) => None
def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_list, opts):
Print out the addresses and/or keys generated by generate_addresses()
By default, prints addresses only
Output can be customized with the following command line options:
print_secret
no_addresses
b16
"""
start = addrlist[0]['num']
end = addrlist[-1]['num']
start = addr_data[0]['num']
end = addr_data[-1]['num']
wif_msg = ""
if ('b16' in opts and 'print_secret' in opts) \
@ -139,21 +140,14 @@ def format_addr_data(addrlist, seed_chksum, opts):
(5 if 'print_secret' in opts else 1) + len(wif_msg)
)
header = """
# MMGen address file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {}.
# A text label of {} characters or less may be added to the right of each
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain any printable ASCII symbol.
""".strip().format(g.proj_name_cap,g.max_addr_label_len)
data = []
if not 'stdout' in opts: data.append(header + "\n")
data.append("%s {" % seed_chksum.upper())
if not 'stdout' in opts: data.append(addrmsgs['addrfile_header'] + "\n")
data.append("# Address data checksum for {}[{}]: {}".format(
seed_id, fmt_addr_list(addr_list), addr_data_chksum))
data.append("# Record this value to a secure location\n")
data.append("%s {" % seed_id.upper())
for el in addrlist:
for el in addr_data:
col1 = el['num']
if 'no_addresses' in opts:
if 'b16' in opts:
@ -179,23 +173,23 @@ def format_addr_data(addrlist, seed_chksum, opts):
def fmt_addr_list(addr_list):
addr_list = list(set(sorted(addr_list)))
prev = addr_list[0]
ret = prev,
for i in addr_list[1:]:
if i == prev + 1:
if i == addr_list[-1]: ret += "-", i
else:
if prev != ret[-1]: ret += "-", prev
ret += ",", i
prev = i
return "".join([str(i) for i in ret])
def write_addr_data_to_file(seed, data, addr_list, opts):
def write_addr_data_to_file(seed, addr_data_str, addr_list, opts):
if 'print_addresses_only' in opts: ext = g.addrfile_ext
elif 'no_addresses' in opts: ext = g.keyfile_ext
@ -211,10 +205,7 @@ def write_addr_data_to_file(seed, data, addr_list, opts):
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
write_to_file(outfile,data)
write_to_file(outfile,addr_data_str)
dtype = "Address" if 'print_addresses_only' in opts else "Key"
msg("%s data saved to file '%s'" % (dtype,outfile))
if __name__ == "__main__":
print fmt_addr_list(sorted(set([1,3,5,2,8,9,10,12,13,14,16])))

View file

@ -18,15 +18,20 @@
"""
config.py: Constants and configuration options for the mmgen suite
"""
author = "Philemon"
email = "<mmgen-py@yandex.com>"
Cdates = '2013-2014'
version = '0.7.4'
quiet,verbose = False,False
min_screen_width = 80
from decimal import Decimal
tx_fee = Decimal("0.001")
max_tx_fee = Decimal("0.1")
tx_fee = Decimal("0.0001")
max_tx_fee = Decimal("0.01")
proj_name = "mmgen"
proj_name_cap = "MMGen"
proj_name = "MMGen"
wallet_ext = "mmdat"
seed_ext = "mmseed"
@ -59,6 +64,7 @@ keyconv_exec = "keyconv"
from os import getenv
debug = True if getenv("MMGEN_DEBUG") else False
no_license = True if getenv("MMGEN_NOLICENSE") else False
bogus_wallet_data = True if getenv("MMGEN_BOGUS_WALLET_DATA") else False
mins_per_block = 8.5
passwd_max_tries = 5

View file

@ -21,13 +21,14 @@ license.py: Show the license
import sys
from mmgen.util import msg, msg_r, get_char
import mmgen.config as g
gpl = {
'warning': """
MMGen Copyright (C) 2013-2014 by Philemon <mmgen-py@yandex.com>. This
MMGen Copyright (C) {g.Cdates} by {g.author} {g.email}. This
program comes with ABSOLUTELY NO WARRANTY. This is free software, and
you are welcome to redistribute it under certain conditions.
""",
""".format(g=g),
'prompt': """
Press 'w' for conditions and warranty info, or 'c' to continue:
""",
@ -586,7 +587,6 @@ copy of the Program in return for a fee.
def do_license_msg(immed=False):
import mmgen.config as g
if g.quiet or g.no_license: return
msg(gpl['warning'])

View file

@ -23,6 +23,7 @@ import sys
import mmgen.bitcoin as bitcoin
from mmgen.util import *
from mmgen.tx import *
commands = {
# "keyconv_compare": ['wif [str]'],
@ -54,44 +55,47 @@ commands = {
"mn_printlist": ['wordlist [str="electrum"]'],
"id8": ['<infile> [str]'],
"id6": ['<infile> [str]'],
"listaccounts": ['minconf [int=1]'],
"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
"getbalance": ['minconf [int=1]'],
"viewtx": ['<MMGen tx file> [str]'],
"check_addrfile": ['<MMGen addr file> [str]']
}
command_help = """
File operations
hexdump - encode data into formatted hexadecimal form (file or stdin)
unhexdump - decode formatted hexadecimal data (file or stdin)
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)
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
hextob58 - convert a hexadecimal number to base 58
b58tohex - convert a base 58 number to hexadecimal
b58randenc - generate a random 32-byte number and convert it to base 58
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
Bitcoin operations:
strtob58 - convert a string to base 58
hextob58 - convert a hexadecimal number to base 58
b58tohex - convert a base 58 number to hexadecimal
b58randenc - generate a random 32-byte number and convert it to base 58
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):
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
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
Bitcoind operations (bitcoind must be running):
listaddresses - show MMGen addresses and their balances
getbalance - like 'bitcoind getbalance' but shows confirmed/unconfirmed,
spendable/unspendable
viewtx - show raw transaction in human-readable form
check_addrfile - compute checksum and address list for MMGen address file
IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're
computed using a different algorithm and are NOT Electrum-compatible!
IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're
computed using a different algorithm and are NOT Electrum-compatible!
"""
def tool_usage(prog_name, command):
@ -170,14 +174,10 @@ 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):
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
o = pretty_hexdump(d, 2, cols, line_nums)
print o
print pretty_hexdump(get_data_from_file(infile,dash=True), 2, cols, line_nums)
def unhexdump(infile):
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
o = decode_pretty_hexdump(d)
sys.stdout.write(o)
sys.stdout.write(decode_pretty_hexdump(get_data_from_file(infile,dash=True)))
def strtob58(s):
enc = bitcoin.b58encode(s)
@ -264,60 +264,79 @@ 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 id8(infile): print make_chksum_8(get_data_from_file(infile,dash=True))
def id6(infile): print make_chksum_6(get_data_from_file(infile,dash=True))
def id6(infile):
d = sys.stdin.read() if infile == "-" else get_data_from_file(infile)
print make_chksum_6(d)
def listaccounts(minconf=1):
# List MMGen addresses and their balances:
def listaddresses(minconf=1,showempty=False):
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]))
addrs = {}
for d in c.listunspent(0):
ma,comment = split2(d.account)
if is_mmgen_addr(ma) and d.confirmations >= minconf:
key = "_".join(ma.split(":"))
if key not in addrs: addrs[key] = [0,comment]
addrs[key][0] += d.amount
# "bitcoind getbalance <account>" can produce a false balance
# (sipa watchonly bitcoind), so use only for empty accounts
if showempty:
# Show accts with not enough confirmations as empty!
# A feature, not a bug!
for (ma,comment),bal in [(split2(a),c.getbalance(a,minconf=minconf))
for a in c.listaccounts(0)]:
if is_mmgen_addr(ma) and bal == 0:
key = "_".join(ma.split(":"))
if key not in addrs: addrs[key] = [0,comment]
fs = "%-{}s %-{}s %s".format(
max([len(k) for k in addrs.keys()]),
max([len(str(addrs[k][1])) for k in addrs.keys()])
)
print fs % ("ADDRESS","COMMENT","BALANCE")
def s_mmgen(ma):
return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *ma.split("_"))
old_sid = ""
for k in sorted(addrs.keys(),key=s_mmgen):
sid,num = k.split("_")
if old_sid and old_sid != sid: print
old_sid = sid
print fs % (sid+":"+num, addrs[k][1], trim_exponent(addrs[k][0]))
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")
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]]
c = d.confirmations
i = 2 if c >= minconf else 1
for key in keys:
if key not in accts: accts[key] = [0,0,0]
for j in ([0] if c == 0 else []) + [i]:
accts[key][j] += d.amount
fs = "{:12} {:<%s} {:<%s} {:<}" % (16,16)
mc,lbl = str(minconf),"confirms"
print fs.format("Wallet","Unconfirmed",
"<%s %s"%(mc,lbl),">=%s %s"%(mc,lbl))
for key in sorted(accts.keys()):
print fs.format(key+":", *[str(trim_exponent(a))+" BTC" for a in accts[key]])
def viewtx(infile):
c = connect_to_bitcoind()
tx_data = get_lines_from_file(infile,"transaction data")
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile)
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
def check_addrfile(infile): parse_addrs_file(infile)

View file

@ -37,7 +37,31 @@ which makes the signing process more complicated. When signing the
transaction, keys for the non-mmgen inputs must be supplied in a separate
file using the '-k' option to mmgen-txsign.
Selected mmgen inputs: %s"""
Selected mmgen inputs: %s""",
'too_many_acct_addresses': """
ERROR: More than one address found for account: "%s".
The tracking "wallet.dat" file appears to have been altered by a non-{g.proj_name}
program. Please restore "wallet.dat" from a backup or create a new wallet
and re-import your addresses.""".strip().format(g=g),
'addrfile_no_data_msg': """
No data found for MMgen address '%s'. Please import this address into
your tracking wallet, or supply an address file for it on the command line.
""".strip(),
'addrfile_warn_msg': """
Warning: no data for address '{mmaddr}' was found in the tracking wallet, so
this information was taken from the user-supplied address file. You're strongly
advised to import this address into your tracking wallet before proceeding with
this transaction. The address will not be tracked until you do so.
""".strip(),
'addrfile_fail_msg': """
No data for MMgen address '{mmaddr}' could be found in either the tracking
wallet or the supplied address file. Please import this address into your
tracking wallet, or supply an address file for it on the command line.
""".strip(),
'no_spendable_outputs': """
No spendable outputs found! Import addresses with balances into your
watch-only wallet using 'mmgen-addrimport' and then re-run this program.
""".strip()
}
# Deleted text:
@ -73,6 +97,7 @@ def trim_exponent(n):
def is_btc_amt(amt):
# amt must be a string!
from decimal import Decimal
try:
@ -94,12 +119,11 @@ def is_btc_amt(amt):
return trim_exponent(ret)
def check_btc_amt(amt):
def normalize_btc_amt(amt):
ret = is_btc_amt(amt)
if ret:
return ret
else:
sys.exit(3)
if ret: return ret
else: sys.exit(3)
def get_bitcoind_cfg_options(cfg_keys):
@ -116,37 +140,24 @@ def get_bitcoind_cfg_options(cfg_keys):
msg("Don't know where to look for 'bitcoin.conf'")
sys.exit(3)
try:
f = open(cfg_file)
except:
msg("Unable to open file '%s' for reading" % cfg_file)
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)
cfg = {}
for line in f.readlines():
s = line.translate(None,"\n\t ").split("=")
for k in cfg_keys:
if s[0] == k: cfg[k] = s[1]
f.close()
for k in cfg_keys:
if not k in cfg:
msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
sys.exit(2)
return cfg
def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
def write_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
tx_id = make_chksum_6(unhexlify(tx)).upper()
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())
data = "%s\n%s\n%s\n%s\n" % (
metadata, tx,
data = "{} {} {}\n{}\n{}\n{}\n".format(
tx_id, send_amt, make_timestamp(),
tx,
repr([i.__dict__ for i in sel_unspent]),
repr(b2m_map)
)
@ -154,17 +165,22 @@ def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
msg("Transaction data saved to file '%s'" % outfile)
def print_signed_tx_to_file(tx,sig_tx,metadata,opts):
def write_signed_tx_to_file(tx,sig_tx,metadata,inputs_data,b2m_map,opts):
tx_id = make_chksum_6(unhexlify(tx)).upper()
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)
data = "{}\n{}\n{}\n{}\n".format(
" ".join(metadata[:2] + [make_timestamp()]),
sig_tx,
repr(inputs_data),
repr(b2m_map)
)
write_to_file(outfile,data,confirm=False)
msg("Signed transaction saved to file '%s'" % outfile)
def print_sent_tx_to_file(tx,metadata,opts):
def write_sent_tx_num_to_file(tx,metadata,opts):
outfile = "tx_{}[{}].out".format(*metadata[:2])
if 'outdir' in opts:
outfile = "%s/%s" % (opts['outdir'], outfile)
@ -176,7 +192,7 @@ 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","MMgen ID",
"Amount (BTC)","Confirms","Age (days)", "Comment")]
"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
@ -199,7 +215,10 @@ def sort_and_view(unspent):
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): return i.account
def s_mmgen(i):
m = parse_mmgen_label(i.account)[0]
if m: return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *m.split(":"))
else: return "G" + i.account
sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
@ -216,7 +235,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
"('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
from copy import deepcopy
print_to_file_msg = ""
write_to_file_msg = ""
msg("")
from mmgen.term import get_terminal_size
@ -233,7 +252,7 @@ Display options: show [D]ays, [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"
a = "Age(d)" if show_days else "Confirms"
a = "Age(d)" if show_days else "Conf."
table_hdr = fs % ("Num","TX id Vout","","Address", "Amount (BTC)",a)
unsp = deepcopy(unspent)
@ -283,14 +302,13 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
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 = ""
msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg)
write_to_file_msg = ""
immed_chars = "atDdAMrgmeqpvw"
skip_prompt = False
while True:
reply = get_char(prompt, immed_chars=immed_chars)
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"
@ -311,7 +329,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
outfile = "listunspent[%s].out" % ",".join(sort_info)
write_to_file(outfile, data)
print_to_file_msg = "Data written to '%s'\n\n" % outfile
write_to_file_msg = "Data written to '%s'\n\n" % outfile
elif reply == 'v':
do_pager("\n".join(out))
continue
@ -332,15 +350,10 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
def parse_mmgen_label(s,check_label_len=False):
if not s: return "",""
try: w1,w2 = s.split(None,1)
except: w1,w2 = s,""
if not is_mmgen_addr(w1): return "",w1
if check_label_len: check_addr_label(w2)
return w1,w2
l = split2(s)
if not is_mmgen_addr(l[0]): return "",s
if check_label_len: check_addr_label(l[1])
return tuple(l)
def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
@ -401,53 +414,51 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
if pager: do_pager(out)
else: msg("\n"+out)
else: print "\n"+out
def parse_tx_data(tx_data,infile):
if len(tx_data) != 4:
try:
metadata,tx_hex,inputs_data,outputs_data = tx_data
except:
msg("'%s': not a transaction file" % infile)
sys.exit(2)
err_fmt = "Transaction %s is invalid"
if len(tx_data[0].split()) != 3:
if len(metadata.split()) != 3:
msg(err_fmt % "metadata")
sys.exit(2)
try: unhexlify(tx_data[1])
try: unhexlify(tx_hex)
except:
msg(err_fmt % "hex data")
sys.exit(2)
else:
if not tx_data:
if not tx_hex:
msg("Transaction is empty!")
sys.exit(2)
try:
inputs_data = eval(tx_data[2])
inputs_data = eval(inputs_data)
except:
msg(err_fmt % "inputs data")
sys.exit(2)
else:
if not inputs_data:
msg("Transaction has no inputs!")
sys.exit(2)
try:
map_data = eval(tx_data[3])
outputs_data = eval(outputs_data)
except:
msg(err_fmt % "mmgen to btc address map data")
sys.exit(2)
return tx_data[0].split(),tx_data[1],inputs_data,map_data
return metadata.split(),tx_hex,inputs_data,outputs_data
def select_outputs(unspent,prompt):
while True:
reply = my_raw_input(prompt,allowed_chars="0123456789 -").strip()
reply = my_raw_input(prompt).strip()
if not reply: continue
@ -462,111 +473,61 @@ def select_outputs(unspent,prompt):
return selected
def is_mmgen_seed(s):
def is_mmgen_seed_id(s):
import re
return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s)
return True if re.match(r"^[0123456789ABCDEF]{8}$",s) else False
def is_mmgen_num(s):
def is_mmgen_idx(s):
import re
return len(s) <= g.mmgen_idx_max_digits \
and re.match(r"^[123456789]+[0123456789]*$",s)
m = g.mmgen_idx_max_digits
return True if re.match(r"^[0123456789]{1,"+str(m)+r"}$",s) else False
def is_mmgen_addr(s):
import re
return len(s) > 9 and s[8] == ':' \
and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \
and len(s[9:]) <= g.mmgen_idx_max_digits \
and re.match(r"^[123456789]+[0123456789]*$",s[9:])
seed_id,idx = split2(s,":")
return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
def is_btc_addr(s):
from mmgen.bitcoin import verify_addr
return verify_addr(s)
def btc_addr_to_mmgen_addr(btc_addr,b2m_map):
if btc_addr in b2m_map:
return b2m_map[btc_addr]
return "",""
def mmgen_addr_to_walletd(c,mmaddr,acct_data):
def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
# We don't want to create a new object, so we'll use append()
if not acct_data:
for i in c.listaccounts():
acct_data.append(i)
for a in acct_data:
if not a: continue
try:
w1,w2 = a.split(None,1)
except:
w1,w2 = a,""
if w1 == mmaddr:
acct = a
break
else:
return "",""
for acct in acct_data:
m,comment = parse_mmgen_label(acct)
if m == mmaddr:
addrlist = c.getaddressesbyaccount(acct)
if len(addrlist) == 1:
return addrlist[0],comment
else:
msg(txmsg['too_many_acct_addresses'] % acct); sys.exit(2)
alist = c.getaddressesbyaccount(acct)
if len(alist) != 1:
msg("""
ERROR: More than one address found for account: "%s".
The tracking "wallet.dat" file appears to have been altered by a non-%s
program. Please restore "wallet.dat" from a backup or create a new wallet
and re-import your addresses.
""".strip() % (acct,g.proj_name_cap))
sys.exit(3)
return alist[0],w2
return "",""
def mmgen_addr_to_addr_data(m,addr_data):
def mmaddr2btcaddr_addrfile(mmaddr,addr_data):
no_data_msg = """
No data found for MMgen address '%s'. Please import this address into
your tracking wallet, or supply an address file for it on the command line.
""".strip() % m
warn_msg = """
Warning: no data for address '%s' exists in the wallet, so it was
taken from the user-supplied address file. You're strongly advised to
import this address into your tracking wallet before proceeding with
this transaction. The address will not be tracked until you do so.
""".strip() % m
fail_msg = """
No data found for MMgen address '%s' in either wallet or supplied
address file. Please import this address into your tracking wallet, or
supply an address file for it on the command line.
""".strip() % m
mmid,mmidx = mmaddr.split(":")
ID,num = m.split(":")
from binascii import unhexlify
try: unhexlify(ID)
except: pass
else:
try: num = int(num)
except: pass
else:
if not addr_data:
msg(no_data_msg)
sys.exit(2)
for i in addr_data:
if ID == i[0]:
for j in i[1]:
if j[0] == num:
msg(warn_msg)
if not user_confirm("Continue anyway?"):
sys.exit(1)
return j[1],(j[2] if len(j) == 3 else "")
msg(fail_msg)
sys.exit(2)
for ad in addr_data:
if mmid == ad[0]:
for j in ad[1]:
if j[0] == mmidx:
msg(txmsg['addrfile_warn_msg'].format(mmaddr=mmaddr))
if not user_confirm("Continue anyway?"):
sys.exit(1)
return j[1:] if len(j) == 3 else (j[1],"")
msg("Invalid format: %s" % m)
sys.exit(3)
msg(txmsg['addrfile_fail_msg'].format(mmaddr=mmaddr))
sys.exit(2)
def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,saved_seeds,opts):
in_maplist = [(i['account'].split()[0],i['address'])
for i in inputs_data if i['account']
and is_mmgen_addr(i['account'].split()[0])]
@ -578,7 +539,7 @@ def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
mmaddrs = [i[0] for i in maplist]
from copy import deepcopy
pairs = get_keys_for_mmgen_addrs(mmaddrs,
deepcopy(infiles),seeds,opts,gen_pairs=True)
infiles,saved_seeds,opts,gen_pairs=True)
for a,b in zip(sorted(pairs),sorted(maplist)):
if a != b:
msg("""
@ -607,6 +568,17 @@ Only ASCII printable characters are permitted.
sys.exit(3)
def check_addr_data_hash(seed_id,addr_data):
from hashlib import new as hashlib_new
addr_data_chksum = make_chksum_8(
" ".join(["{} {}".format(*d[:2]) for d in addr_data]), sep=True
)
from mmgen.addr import fmt_addr_list
fl = fmt_addr_list([int(a[0]) for a in addr_data])
msg("Computed address data checksum for '{}[{}]': {}".format(
seed_id,fl,addr_data_chksum))
msg("Check this value against your records")
def parse_addrs_file(f):
lines = get_lines_from_file(f,"address data",remove_comments=True)
@ -623,14 +595,14 @@ def parse_addrs_file(f):
msg("'%s': invalid first line" % lines[0])
elif cbrace != '}':
msg("'%s': invalid last line" % cbrace)
elif not is_mmgen_seed(seed_id):
elif not is_mmgen_seed_id(seed_id):
msg("'%s': invalid Seed ID" % seed_id)
else:
ret = []
addr_data = []
for i in lines[1:-1]:
d = i.split(None,2)
if not is_mmgen_num(d[0]):
if not is_mmgen_idx(d[0]):
msg("'%s': invalid address num. in line: %s" % (d[0],d))
sys.exit(3)
@ -641,9 +613,11 @@ def parse_addrs_file(f):
if len(d) == 3:
check_addr_label(d[2])
ret.append(tuple(d))
addr_data.append(tuple(d))
return seed_id,ret
check_addr_data_hash(seed_id,addr_data)
return seed_id,addr_data
sys.exit(3)
@ -664,62 +638,46 @@ def sign_transaction(c,tx_hex,sig_data,keys=None):
return sig_tx
def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts):
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
if seed_id in saved_seeds.keys():
return saved_seeds[seed_id]
while True:
if infiles:
seed = get_seed_retry(infiles.pop(0),opts)
elif "from_brain" in opts or "from_mnemonic" in opts \
or "from_seed" in opts or "from_incog" in opts:
msg("Need data for seed ID %s" % seed_id)
seed = get_seed_retry("",opts)
else:
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
sys.exit(2)
s_id = make_chksum_8(seed)
saved_seeds[s_id] = seed
if s_id == seed_id: return seed
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts,gen_pairs=False):
seed_ids = list(set([i[:8] for i in mmgen_addrs]))
seed_ids_save = seed_ids[0:] # deep copy
ret = []
seeds_keys = [i for i in seed_ids if i in seeds]
for seed_id in seed_ids:
# Returns only if seed is found
seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts)
while seed_ids:
if seeds_keys:
seed = seeds[seeds_keys.pop(0)]
addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
from mmgen.addr import generate_keys,generate_addrs
if gen_pairs:
o = {"gen_what":"addresses"}
ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
for i in generate_addrs(seed, addr_ids, o)]
else:
infile = False
if infiles:
infile = infiles.pop(0)
seed = get_seed_retry(infile,opts)
elif "from_brain" in opts or "from_mnemonic" in opts \
or "from_seed" in opts or "from_incog" in opts:
msg("Need data for seed ID %s" % seed_ids[0])
seed = get_seed_retry("",opts)
else:
b,p,v = ("A seed","","is") if len(seed_ids) == 1 \
else ("Seed","s","are")
msg("ERROR: %s source%s %s required for the following seed ID%s: %s"%
(b,p,v,p," ".join(seed_ids)))
sys.exit(2)
seed_id = make_chksum_8(seed)
if seed_id in seed_ids:
seed_ids.remove(seed_id)
addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
seeds[seed_id] = seed
from mmgen.addr import generate_keys,generate_addrs
if gen_pairs:
o = {"gen_what":"addresses"}
ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
for i in generate_addrs(seed, addr_ids, o)]
else:
ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
else:
if seed_id in seed_ids_save:
msg_r("Ignoring duplicate seed source")
if infile: msg(" '%s'" % infile)
else: msg(" for ID %s" % seed_id)
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if "from_incog" in opts or infile.split(".")[-1] == g.incog_ext:
msg(
"""Incorrect hash preset, password or incognito wallet data
Trying again...""")
infiles.insert(0,infile) # ugly!
elif infile:
msg("Invalid input file '%s'" % infile)
sys.exit(2)
ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
return ret
@ -759,7 +717,7 @@ def preverify_keys(addrs_orig, keys_orig):
if len(keys) < len(addrs):
msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
(len(keys),g.proj_name_cap,len(addrs)))
(len(keys),g.proj_name,len(addrs)))
sys.exit(2)
import mmgen.bitcoin as b
@ -803,7 +761,7 @@ def preverify_keys(addrs_orig, keys_orig):
if addrs:
s = "" if len(addrs) == 1 else "es"
msg("No keys found for the following non-%s address%s:" %
(g.proj_name_cap,s))
(g.proj_name,s))
print " %s" % "\n ".join(addrs)
sys.exit(2)

View file

@ -43,10 +43,10 @@ def vmsg_r(s):
def bail(): sys.exit(9)
def get_extension(f):
try: return f.split(".")[-1]
except: return ""
import os
return os.path.splitext(f)[1][1:]
def my_raw_input(prompt,echo=True,allowed_chars=""):
def my_raw_input(prompt,echo=True):
try:
if echo:
reply = raw_input(prompt)
@ -61,33 +61,10 @@ def my_raw_input(prompt,echo=True,allowed_chars=""):
return reply
def my_raw_input_old(prompt,echo=True,allowed_chars=""):
msg_r(prompt)
reply = ""
while True:
ch = get_char(immed_chars="ALL_EXCEPT_ENTER")
if allowed_chars and ch not in allowed_chars+"\n\r\b"+chr(0x7f):
continue
if echo:
if ch in "\b"+chr(0x7f): # WIP
pass
# reply.pop(0)
else: msg_r(ch)
if ch in "\n\r":
if not echo: msg("")
break
reply += ch
return reply
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
else: # Shouldn't be here
msg("%s: invalid 'hash_preset' value" % hash_preset)
sys.exit(3)
@ -126,7 +103,7 @@ cmessages = {
'unencrypted_secret_keys': """
This program generates secret keys from your {} seed, outputting them in
UNENCRYPTED form. Generate only the key(s) you need and guard them carefully.
""".format(g.proj_name_cap),
""".format(g.proj_name),
'brain_warning': """
############################## EXPERTS ONLY! ##############################
@ -154,12 +131,10 @@ def confirm_or_exit(message, question, expect="YES"):
conf_msg = "Type uppercase '%s' to confirm: " % expect
if question[0].isupper():
prompt = question + " " + conf_msg
else:
prompt = "Are you sure you want to %s?\n%s" % (question,conf_msg)
p = question+" "+conf_msg if question[0].isupper() else \
"Are you sure you want to %s?\n%s" % (question,conf_msg)
if my_raw_input(prompt).strip() != expect:
if my_raw_input(p).strip() != expect:
msg("Exiting at user request")
sys.exit(2)
@ -196,9 +171,10 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
else: msg_r("\r")
def make_chksum_8(s):
def make_chksum_8(s,sep=False):
from hashlib import sha256
return sha256(sha256(s).digest()).hexdigest()[:8].upper()
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
return "{} {}".format(s[:4],s[4:]) if sep else s
def make_chksum_6(s):
from hashlib import sha256
@ -222,12 +198,14 @@ def check_infile(f):
msg("Requested input file '%s' is unreadable by you" % f)
sys.exit(1)
return True
def _validate_addr_num(n):
try: n = int(n)
except:
msg("'%s': invalid argument for address" % n)
msg("'%s': address must be an integer" % n)
return False
if n < 1:
@ -321,7 +299,7 @@ def _get_seed_from_brain_passphrase(words,opts):
def encrypt_seed(seed, key, iv=1):
"""
Encrypt a seed for an {} deterministic wallet
""".format(g.proj_name_cap)
""".format(g.proj_name)
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
from Crypto.Cipher import AES
@ -439,6 +417,13 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
): msg(fs.format(*i))
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))]
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 col4(s):
nondiv = 1 if len(s) % 4 else 0
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
@ -578,7 +563,7 @@ def get_data_from_wallet(infile,silent=False):
# Don't make this a qmsg: User will be prompted for passphrase and must see
# the filename.
if not silent:
msg("Getting {} wallet data from file '{}'".format(g.proj_name_cap,infile))
msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile))
f = open_file_or_exit(infile, 'r')
@ -653,7 +638,8 @@ def get_lines_from_file(infile,what="",remove_comments=False):
return lines
def get_data_from_file(infile,what="data"):
def get_data_from_file(infile,what="data",dash=False):
if dash and infile == "-": return sys.stdin.read()
qmsg("Getting %s from file '%s'" % (what,infile))
f = open_file_or_exit(infile,'r')
data = f.read()
@ -682,7 +668,7 @@ def _get_seed_from_seed_data(words):
vmsg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
return seed
else:
msg("Invalid checksum for {} seed".format(g.proj_name_cap))
msg("Invalid checksum for {} seed".format(g.proj_name))
return False
@ -717,7 +703,7 @@ def get_bitcoind_passphrase(prompt,opts):
def get_seed_from_wallet(
infile,
opts,
prompt="Enter {} wallet passphrase: ".format(g.proj_name_cap),
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False
):
@ -770,7 +756,7 @@ def get_hidden_incog_data(opts):
def get_seed_from_incog_wallet(
infile,
opts,
prompt="Enter %s wallet passphrase: " % g.proj_name_cap,
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
silent=False,
hex_input=False
):
@ -806,7 +792,7 @@ def get_seed_from_incog_wallet(
msg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
while True:
p = "Enter hash preset for %s wallet (default='%s'): "
hp = my_raw_input(p % (g.proj_name_cap, g.hash_preset))
hp = my_raw_input(p % (g.proj_name, g.hash_preset))
if not hp:
hp = g.hash_preset; break
elif hp in g.hash_presets:
@ -890,7 +876,7 @@ def _get_words(infile,what,prompt,opts):
def get_seed(infile,opts,silent=False):
ext = infile.split(".")[-1]
ext = get_extension(infile)
if ext == g.mn_ext: source = "mnemonic"
elif ext == g.brain_ext: source = "brainwallet"
@ -922,7 +908,7 @@ def get_seed(infile,opts,silent=False):
if not g.quiet:
confirm_or_exit(
cmessages['brain_warning'].format(
g.proj_name_cap, *_get_from_brain_opt_params(opts)),
g.proj_name, *_get_from_brain_opt_params(opts)),
"continue")
prompt = "Enter brainwallet passphrase: "
words = _get_words(infile,"brainwallet data",prompt,opts)

View file

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