* better options handling
* code cleanups * new commands in 'mmgen-tool' * tx view in 'mmgen-txsend'
This commit is contained in:
parent
242ff6acfa
commit
38e0a954d0
19 changed files with 579 additions and 661 deletions
1
MANIFEST
1
MANIFEST
|
|
@ -41,4 +41,3 @@ mmgen/tests/mnemonic.py
|
|||
mmgen/tests/test.py
|
||||
mmgen/tests/util.py
|
||||
mmgen/tests/walletgen.py
|
||||
test/test.py
|
||||
|
|
|
|||
108
mmgen-addrgen
108
mmgen-addrgen
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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...")
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
47
mmgen-txsend
47
mmgen-txsend
|
|
@ -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)
|
||||
|
|
|
|||
47
mmgen-txsign
47
mmgen-txsign
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
120
mmgen/Opts.py
120
mmgen/Opts.py
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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])))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
|
|
|||
189
mmgen/tool.py
189
mmgen/tool.py
|
|
@ -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)
|
||||
|
|
|
|||
354
mmgen/tx.py
354
mmgen/tx.py
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue