New script launcher, better KeyboardInterrupt handling, bugfixes in term.py

This commit is contained in:
philemon 2014-08-05 22:24:27 +04:00
commit 2b183c18f3
36 changed files with 3247 additions and 2975 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,183 +17,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-addrgen: Generate a list or range of addresses from a mmgen
deterministic wallet.
Call as 'btc-keygen' to allow key generation.
mmgen-addrgen: Generate a series or range of addresses from an MMGen
deterministic wallet
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
from mmgen.addr import *
from mmgen.tx import make_addr_data_chksum
what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
help_data = {
'prog_name': g.prog_name,
'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
mnemonic, seed or password""".format(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
-c, --save-checksum Save address list checksum to file
-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: {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 {what} to stdout
-v, --verbose Produce more verbose output{}
-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 {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 {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-f, --flat-list Produce a flat list of keys suitable for use with" +
"\n '{}-txsign'".format(g.proj_name.lower()),
"\n-x, --b16 Print secret keys in hexadecimal too"
)
if what == "keys" else ("","","")),
seed_lens=", ".join([str(i) for i in g.seed_lens]),
what=what, g=g
),
'notes': """
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.
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.
For passphrases all combinations of whitespace are equal, and leading and
trailing space are ignored. This permits reading passphrase data from a
multi-line file with free spacing and indentation. This is particularly
convenient for long brainwallet passphrases, for example.
BRAINWALLET NOTE:
As brainwallets require especially strong hashing to thwart dictionary
attacks, the brainwallet hash preset must be specified by the user, using
the 'p' parameter of the '--from-brain' option
The '--from-brain' option also requires the user to specify a seed length
(the 'l' parameter)
For a brainwallet passphrase to always generate the same keys and addresses,
the same 'l' and 'p' parameters to '--from-brain' must be used in all future
invocations with that passphrase
""".format("\n\nBy default, both addresses and secret keys are generated."
if what == "keys" else "")
}
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
if 'quiet' in opts: g.quiet = True
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1 and (
'from_mnemonic' in opts
or 'from_brain' in opts
or 'from_seed' in opts
or 'from_incog_hidden' in opts
):
infile,addr_idx_arg = "",cmd_args[0]
elif len(cmd_args) == 2:
infile,addr_idx_arg = cmd_args
check_infile(infile)
else: usage(help_data)
addr_idxs = parse_address_list(addr_idx_arg)
if not addr_idxs: sys.exit(2)
do_license_msg()
# Interact with user:
if what == "keys" and not g.quiet:
confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
# Generate data:
seed = get_seed_retry(infile,opts)
seed_id = make_chksum_8(seed)
for l in (
('flat_list', 'no_addresses'),
('flat_list', 'b16'),
): warn_incompatible_opts(opts,l)
opts['gen_what'] = \
("addrs") if what == "addresses" else (
("keys") if 'no_addresses' in opts else ("addrs","keys"))
addr_data = generate_addrs(seed, addr_idxs, opts)
addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
addr_data_str = format_addr_data(
addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
if 'flat_list' in opts:
confirm = False if g.quiet else True
outfile = "%s.%s" % (outfile_base,g.keylist_ext)
if (user_confirm("Encrypt key list?")):
enc_data = mmgen_encrypt(addr_data_str,"",opts)
outfile += "."+g.mmenc_ext
write_to_file(outfile,enc_data,opts,"encrypted key list",confirm,True)
else:
write_to_file(outfile,addr_data_str,opts,"key list",confirm,True)
sys.exit()
# Output data:
if 'stdout' in opts:
confirm = True if (what == "keys" and not g.quiet) else False
write_to_stdout(addr_data_str,what,confirm)
elif not sys.stdout.isatty():
write_to_stdout(addr_data_str,what,confirm=False)
else:
confirm = False if g.quiet else True
outfile = outfile_base + "." + (
g.keylist_ext if 'flat_list' in opts else (
g.keyfile_ext if opts['gen_what'] == ("keys") else (
g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys")))
write_to_file(outfile,addr_data_str,opts,what,confirm,True)
if 'addrs' in opts['gen_what']:
msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
if 'save_checksum' in opts:
a = "address data checksum"
write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,confirm,True)
else:
qmsg("This checksum will be used to verify the address file in the future.")
qmsg("Record it to a safe location.")
import mmgen.main
mmgen.main.main("addrgen")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,126 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-addrimport: Import addresses into a bitcoind watching wallet.
mmgen-addrimport: Import addresses into a MMGen bitcoind watching wallet
"""
import sys
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.tx import connect_to_bitcoind,parse_addrs_file
help_data = {
'prog_name': g.prog_name,
'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind
watching wallet""".format(pnm=g.proj_name),
'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'
-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.
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
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)
if cmd_args:
check_infile(cmd_args[0])
seed_id,addr_data = parse_addrs_file(cmd_args[0])
else:
seed_id,addr_data = "",[]
if 'addrlist' in opts:
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
trim_comments=True)
addr_data += [(None,l) for l in lines]
from mmgen.bitcoin import verify_addr
qmsg_r("Validating addresses...")
for i in addr_data:
if not verify_addr(i[1],verbose=True):
msg("%s: invalid address" % i)
sys.exit(2)
qmsg("OK")
import mmgen.config as g
g.http_timeout = 3600
c = connect_to_bitcoind()
m = """
WARNING: You've chosen the '--rescan' option. Rescanning the block chain is
necessary only if an address you're importing is already on the block chain
and has a balance. Note that the rescanning process is very slow (>30 min.
for each imported address on a low-powered computer).
""".strip() if "rescan" in opts else """
WARNING: If any of the addresses you're importing is already on the block chain
and has a balance, you must exit the program now and rerun it using the
'--rescan' option. Otherwise you may ignore this message and continue.
""".strip()
if g.quiet: m = ""
confirm_or_exit(m, "continue", expect="YES")
err_flag = False
def import_address(addr,label,rescan):
try:
c.importaddress(addr,label,rescan)
except:
global err_flag
err_flag = True
w1 = len(str(len(addr_data))) * 2 + 2
w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
if "rescan" in opts:
import threading
import time
msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s"
else:
msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s"
msg("Importing addresses")
for n,i in enumerate(addr_data):
if i[0]:
comment = " " + i[2] if len(i) == 3 else ""
label = "%s:%s%s" % (seed_id,i[0],comment)
else: label = "non-mmgen"
if "rescan" in opts:
t = threading.Thread(target=import_address, args=(i[1],label,True))
t.daemon = True
t.start()
start = int(time.time())
while True:
if t.is_alive():
elapsed = int(time.time() - start)
msg_r(msg_fmt % (
secs_to_hms(elapsed),
("%s/%s:" % (n+1,len(addr_data))),
i[1], "(" + label + ")"
)
)
time.sleep(1)
else:
if err_flag: msg("\nImport failed"); sys.exit(2)
msg("\nOK")
break
else:
import_address(i[1],label,rescan=False)
msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
i[1], "(" + label + ")"))
if err_flag: msg("\nImport failed"); sys.exit(2)
msg(" - OK")
import mmgen.main
mmgen.main.main("addrimport")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,114 +15,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or
hash preset
"""
import sys
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import *
import mmgen.config as g
help_data = {
'prog_name': g.prog_name,
'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
-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: '{g.hash_preset}')
-P, --passwd-file= f Get new passphrase from file 'f'
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-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
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'show_hash_presets' in opts: show_hash_presets()
if len(cmd_args) != 1:
msg("One input file must be specified")
sys.exit(2)
infile = cmd_args[0]
# Old key:
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
seed_id,key_id = metadata[:2]
# Repeat on incorrect pw entry
prompt = "Enter %spassphrase: " % (""
if 'keep_old_passphrase' in opts else "old ")
while True:
passwd = get_mmgen_passphrase(prompt,{})
key = make_key(passwd, salt, hash_preset)
seed = decrypt_seed(enc_seed, key, seed_id, key_id)
if seed: break
changed = {}
if 'label' in opts:
if opts['label'] != label:
msg("Label changed: '%s' -> '%s'" % (label, opts['label']))
changed['label'] = True
else:
msg("Label is unchanged: '%s'" % (label))
else: opts['label'] = label # Copy the old label
if 'hash_preset' in opts:
if hash_preset != opts['hash_preset']:
qmsg("Hash preset has changed (%s -> %s)" %
(hash_preset, opts['hash_preset']))
changed['preset'] = True
else:
msg("Hash preset is unchanged")
else:
opts['hash_preset'] = hash_preset
if 'keep_old_passphrase' in opts:
msg("Keeping old passphrase by user request")
else:
new_passwd = get_new_passphrase("new passphrase", opts)
if new_passwd == passwd:
qmsg("Passphrase is unchanged")
else:
qmsg("Passphrase has changed")
passwd = new_passwd
changed['passwd'] = True
if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
qmsg("Will update salt and key ID")
from hashlib import sha256
salt = sha256(salt + get_random(128,opts)).digest()[:g.salt_len]
key = make_key(passwd, salt, opts['hash_preset'])
new_key_id = make_chksum_8(key)
qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
key_id = new_key_id
enc_seed = encrypt_seed(seed, key)
elif not 'label' in changed:
msg("Data unchanged. No file will be written")
sys.exit(2)
write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts)
import mmgen.main
mmgen.main.main("passchg")

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,57 +15,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-tool: Perform various Bitcoin-related operations - part of the MMGen suite
mmgen-tool: Perform various Bitcoin-related operations.
Part of the MMGen suite
"""
import sys
import mmgen.config as g
import mmgen.tool as tool
from mmgen.Opts import *
help_data = {
'prog_name': g.prog_name,
'desc': "Perform various BTC-related operations",
'usage': "[opts] <command> <command args>",
'options': """
-d, --outdir= d Specify an alternate directory 'd' for output
-h, --help Print this help message
-q, --quiet Produce quieter output
-r, --usr-randchars=n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-v, --verbose Produce more verbose output
""".format(g=g),
'notes': """
COMMANDS:{}
Type '{} <command> --help for usage information on a particular
command
""".format(tool.command_help,g.prog_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if len(cmd_args) < 1:
usage(help_data)
sys.exit(1)
command = cmd_args.pop(0)
if command not in tool.commands.keys():
msg("'%s': No such command" % command)
sys.exit(1)
if cmd_args and cmd_args[0] == '--help':
tool.tool_usage(g.prog_name, command)
sys.exit(0)
args = tool.process_args(g.prog_name, command, cmd_args)
tool.opts = opts
#print command + "(" + ", ".join(args) + ")"
eval("tool." + command + "(" + ", ".join(args) + ")")
import mmgen.main
mmgen.main.main("tool")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,207 +15,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txcreate: Create a BTC transaction, sending to specified addresses
mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs
to MMGen- or non-MMGen outputs
"""
import sys
from decimal import Decimal
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg, msg_r, user_confirm
help_data = {
'prog_name': g.prog_name,
'desc': "Create a BTC transaction with outputs to specified addresses",
'usage': "[opts] <addr,amt> ... [change addr] [addr file] ...",
'options': """
-h, --help Print this help message
-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
""".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 {g.mins_per_block} minutes.
Addresses on the command line can be Bitcoin addresses or {pnm} 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.
""".format(g=g,pnm=g.proj_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
c = connect_to_bitcoind()
if not 'info' in opts:
do_license_msg(immed=True)
tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
cmd_args = set(cmd_args) - set(addrfiles)
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:
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 cmd_args:
if "," in a:
a1,a2 = a.split(",")
if is_btc_addr(a1):
btcaddr = a1
elif is_mmgen_addr(a1):
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] = normalize_btc_amt(a2)
else:
msg("%s: invalid amount in argument '%s'" % (a2,a))
sys.exit(2)
elif is_mmgen_addr(a) or is_btc_addr(a):
if change_addr:
msg("ERROR: More than one change address specified: %s, %s" %
(change_addr, a))
sys.exit(2)
change_addr = a if is_btc_addr(a) else \
mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map)
tx_out[change_addr] = 0
else:
msg("%s: unrecognized argument" % a)
sys.exit(2)
if not tx_out:
msg("At least one output must be specified on the command line")
sys.exit(2)
tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.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)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
#if False:
if g.bogus_wallet_data:
import mmgen.rpc
us = eval(get_data_from_file(g.bogus_wallet_data))
else:
us = c.listunspent()
if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
unspent = sort_and_view(us,opts)
total = trim_exponent(sum([i.amount for i in unspent]))
msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
if 'info' in opts: sys.exit(0)
send_amt = sum([tx_out[i] for i in tx_out.keys()])
msg("Total amount to spend: %s%s" % (
(send_amt or "Unknown")," BTC" if send_amt else ""))
while True:
sel_nums = select_outputs(unspent,
"Enter a range or space-separated list of outputs to spend: ")
msg("Selected output%s: %s" %
(("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums))
)
sel_unspent = [unspent[i-1] for i in sel_nums]
mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
mmaddrs.discard("")
if mmaddrs and len(mmaddrs) < len(sel_unspent):
msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
if not user_confirm("Accept?"):
continue
total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
change = trim_exponent(total_in - (send_amt + tx_fee))
if change >= 0:
prompt = "Transaction produces %s BTC in change. OK?" % change
if user_confirm(prompt,default_yes=True):
break
else:
msg(txmsg['not_enough_btc'] % change)
if change > 0 and not change_addr:
msg(txmsg['throwaway_change'] % change)
sys.exit(2)
if change_addr in tx_out and not change:
msg("Warning: Change address will be unused as transaction produces no change")
del tx_out[change_addr]
for k,v in tx_out.items(): tx_out[k] = float(v)
if change > 0: tx_out[change_addr] = float(change)
tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
if g.debug:
print "tx_in:", repr(tx_in)
print "tx_out:", repr(tx_out)
tx_hex = c.createrawtransaction(tx_in,tx_out)
qmsg("Transaction successfully created")
prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
pager = True if reply in "Vv" else False
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
prompt = "Save transaction?"
if user_confirm(prompt,default_yes=True):
amt = send_amt or change
tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
data = "{} {} {}\n{}\n{}\n{}\n".format(
tx_id, amt, make_timestamp(),
tx_hex,
repr([i.__dict__ for i in sel_unspent]),
repr(b2m_map)
)
write_to_file(outfile,data,opts,"transaction",False,True)
else:
msg("Transaction not saved")
import mmgen.main
mmgen.main.main("txcreate")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,72 +15,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txsend: Broadcast a Bitcoin transaction to the network
mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit
help_data = {
'prog_name': g.prog_name,
'desc': "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()),
'usage': "[opts] <signed transaction file>",
'options': """
-h, --help Print this help message
-d, --outdir= d Specify an alternate directory 'd' for output
-q, --quiet Suppress warnings; overwrite files without prompting
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if len(cmd_args) == 1:
infile = cmd_args[0]; check_infile(infile)
else: usage(help_data)
# Begin execution
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"
if g.quiet: warn,expect = "","YES"
confirm_or_exit(warn, what, expect)
msg("Sending transaction")
try:
tx_id = c.sendrawtransaction(tx_hex)
except:
msg("Unable to send transaction")
sys.exit(3)
msg("Transaction sent: %s" % tx_id)
of = "tx_{}[{}].out".format(*metadata[:2])
write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True)
import mmgen.main
mmgen.main.main("txsend")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,198 +15,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate
mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg,qmsg
help_data = {
'prog_name': g.prog_name,
'desc': "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()),
'usage': "[opts] <transaction file> .. [mmgen wallet/seed/words/brainwallet file] .. [addrfile] ..",
'options': """
-h, --help Print this help message
-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= f Provide additional keys for non-{pnm} addresses
-K, --all-keys-from-file=f Like '-k', only use the keyfile as key source
for ALL inputs, including {pnm} ones. Can be used
for online signing without an {pnm} seed source.
{pnm}-to-BTC mappings can optionally be verified
using address file(s) listed on the command line
-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,
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
'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 .{g.seed_ext} format
""".format(g=g,pnm=g.proj_name),
'notes': """
Transactions with either {pnm} or non-{pnm} input addresses may be signed.
For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source.
For {pnm} inputs, key data is generated from your seed as with the
{pnl}-addrgen and {pnl}-keygen utilities.
Data for the --from-<what> options will be taken from a file if a second
file is specified on the command line. Otherwise, the user will be
prompted to enter the data.
In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm}
keys must be supplied in a separate file (WIF format, one key per line)
using the '--keys-from-file' option. Alternatively, one may get keys from
a running bitcoind using the '--force-wallet-dat' option. First import the
required {pnm} keys using 'bitcoind importprivkey'.
For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address
mappings are verified. Therefore, seed material for these addresses must
be supplied on the command line (but see '--all-keys-from-file').
Seed data supplied in files must have the following extensions:
wallet: '.{g.wallet_ext}'
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_ext}'
brainwallet: '.{g.brain_ext}'
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower())
}
opts,infiles = parse_opts(sys.argv,help_data)
for l in (
('tx_id', 'info'),
('keys_from_file','all_keys_from_file')
): warn_incompatible_opts(opts,l)
if "quiet" in opts: g.quiet = True
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
if 'all_keys_from_file' in opts:
opts['keys_from_file'] = opts['all_keys_from_file']
opts['skip_key_preverify'] = True
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
c = connect_to_bitcoind()
saved_seeds = {}
tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext]
infiles = list(set(infiles) - set(tx_files) - set(addrfiles))
if not "info" in opts: do_license_msg(immed=True)
if 'keys_from_file' in opts:
from mmgen.crypto import mmgen_decrypt
fn = opts['keys_from_file']
if get_extension(fn) == g.mmenc_ext:
enc_d = get_data_from_file(fn,"encrypted keylist")
dec_d = mmgen_decrypt(enc_d,"",opts)
if dec_d:
keys_from_file = remove_comments(dec_d.split("\n"))
else:
msg("Decryption of encrypted keylist failed")
sys.exit(2)
else:
keys_from_file = get_lines_from_file(fn,"key data",trim_comments=True)
else: keys_from_file = []
for tx_file in tx_files:
m = "" if 'tx_id' in opts else "transaction data"
tx_data = get_lines_from_file(tx_file,m)
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
qmsg("Successfully opened transaction file '%s'" % tx_file)
if 'tx_id' in opts:
msg(metadata[0])
sys.exit(0)
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
sys.exit(0)
# Are inputs mmgen addresses?
mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
if 'all_keys_from_file' in opts: other_inputs = inputs_data
keys = keys_from_file
if other_inputs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_inputs)
sys.exit(2)
if other_inputs and keys and not 'skip_key_preverify' in opts:
a = [i['address'] for i in other_inputs]
preverify_keys(a, keys)
opts['skip_key_preverify'] = True
if 'all_keys_from_file' in opts:
if addrfiles:
check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles)
else:
confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue")
else:
check_mmgen_to_btc_addr_mappings(
mmgen_inputs,b2m_map,infiles,saved_seeds,opts)
if len(tx_files) > 1:
msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files)))
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
p = True if reply in "Vv" else False
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
sig_data = [
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
for i in inputs_data]
if mmgen_inputs and not 'all_keys_from_file' in opts:
ml = [i['account'].split()[0] for i in mmgen_inputs]
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)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_inputs:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
if sig_tx['complete']:
prompt = "OK\nSave signed transaction?"
if user_confirm(prompt,default_yes=True):
outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
data = "{}\n{}\n{}\n{}\n".format(
" ".join(metadata[:2] + [make_timestamp()]),
sig_tx['hex'],
repr(inputs_data),
repr(b2m_map)
)
write_to_file(outfile,data,opts,"signed transaction",True,True)
else:
msg("failed\nSome keys were missing. Transaction could not be signed.")
sys.exit(3)
import mmgen.main
mmgen.main.main("txsign")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,102 +15,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-walletchk: Check integrity of a mmgen deterministic wallet, display
information about it and export seed and mnemonic data
mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display
information about it and export it to various formats
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
help_data = {
'prog_name': g.prog_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
-e, --echo-passphrase Print passphrase to screen when typing it
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-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'
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
""".format(g=g),
'notes': """
Since good randomness is particularly important for incognito wallets,
the '--usr-randchars' option is turned on by default to gather additional
entropy from the user when one of the '--export-incog*' options is
selected. If you fully trust your OS's random number generator and wish
to disable this option, then specify '-r0' on the command line.
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
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
if len(cmd_args) != 1: usage(help_data)
check_infile(cmd_args[0])
if 'export_mnemonic' in opts:
qmsg("Exporting mnemonic data to file by user request")
elif 'export_seed' in opts:
qmsg("Exporting seed data to file by user request")
elif 'export_incog' in opts:
if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
qmsg("Exporting wallet to incognito format by user request")
incog_enc,seed_id,key_id,iv_id,preset = \
wallet_to_incog_data(cmd_args[0],opts)
if "export_incog_hidden" in opts:
export_to_hidden_incog(incog_enc,opts)
else:
seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8
fn = "%s-%s-%s[%s,%s].%s" % (
seed_id, key_id, iv_id, seed_len, preset,
g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext
)
data = pretty_hexdump(incog_enc,2,8,line_nums=False) \
if "export_incog_hex" in opts else incog_enc
export_to_file(fn, data, opts, "incognito wallet data")
sys.exit()
seed = get_seed_from_wallet(cmd_args[0], opts)
if seed: qmsg("Wallet is OK")
else:
msg("Error opening wallet")
sys.exit(2)
if 'export_mnemonic' in opts:
wl = get_default_wordlist()
from mmgen.mnemonic import get_mnemonic_from_seed
p = True if g.debug else False
mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data")
elif 'export_seed' in opts:
from mmgen.bitcoin import b58encode_pad
data = col4(b58encode_pad(seed))
chk = make_chksum_6(b58encode_pad(seed))
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data")
import mmgen.main
mmgen.main.main("walletchk")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,136 +15,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-walletgen: Generate a mmgen deterministic wallet
mmgen-walletgen: Generate an MMGen deterministic wallet
"""
import sys, os
from hashlib import sha256
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
help_data = {
'prog_name': g.prog_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
-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: {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: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Produce quieter output; overwrite files without
prompting
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-v, --verbose Produce more verbose output
-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 .{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),
{g.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.
For passphrases all combinations of whitespace are equal, and leading and
trailing space are ignored. This permits reading passphrase data from a
multi-line file with free spacing and indentation. This is particularly
convenient for long brainwallet passphrases, for example.
Since good randomness is particularly important when generating wallets,
the '--usr-randchars' option is turned on by default to gather additional
entropy from the user. If you fully trust your OS's random number gener-
ator and wish to disable this option, specify '-r0' on the command line.
BRAINWALLET NOTE:
As brainwallets require especially strong hashing to thwart dictionary
attacks, the brainwallet hash preset must be specified by the user, using
the 'p' parameter of the '--from-brain' option. This preset should be
stronger than the one used for hashing the seed (i.e. the default value or
the one specified in the '--hash-preset' option).
The '--from-brain' option also requires the user to specify a seed length
(the 'l' parameter), which overrides both the default and any one given in
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(g=g)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'quiet' in opts: g.quiet = True
if 'verbose' in opts: g.verbose = True
if 'show_hash_presets' in opts: show_hash_presets()
if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1:
infile = cmd_args[0]
check_infile(infile)
ext = infile.split(".")[-1]
ok_exts = g.seedfile_exts
for e in ok_exts:
if e == ext: break
else:
msg(
"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
sys.exit(1)
elif len(cmd_args) == 0:
infile = ""
else: usage(help_data)
# Begin execution
do_license_msg()
if 'from_brain' in opts and not g.quiet:
confirm_or_exit(cmessages['brain_warning'].format(
g.proj_name, *get_from_brain_opt_params(opts)),
"continue")
for i in 'from_mnemonic','from_brain','from_seed','from_incog':
if infile or (i in opts):
seed = get_seed_retry(infile,opts)
if "from_incog" in opts or get_extension(infile) == g.incog_ext:
qmsg(cmessages['incog'] % make_chksum_8(seed))
else: qmsg("")
break
else:
# Truncate random data for smaller seed lengths
seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8]
salt = sha256(get_random(128,opts)).digest()[:g.salt_len]
qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset'])
passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
key = make_key(passwd, salt, opts['hash_preset'])
enc_seed = encrypt_seed(seed, key)
write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)
import mmgen.main
mmgen.main.main("walletgen")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,6 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Opts.py: Option handling routines for the MMGen suite
"""
import sys
import mmgen.config as g
import mmgen.opt.Opts
@ -65,6 +69,9 @@ def parse_opts(argv,help_data):
if v in opts: typeconvert_override_var(opts,v)
else: opts[v] = eval("g."+v)
if "verbose" in opts: g.verbose = True
if "quiet" in opts: g.quiet = True
if g.debug: print "opts after typeconvert: %s" % opts
return opts,args

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
MMGen = Multi-Mode GENerator, a Bitcoin cold storage/tracking solution for
the command line
"""
__all__ = [
'rpc',

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
addr.py: Address generation/display routines for mmgen suite
addr.py: Address generation/display routines for the MMGen suite
"""
import sys
@ -68,8 +69,8 @@ def generate_addrs(seed, addrnums, opts):
from subprocess import Popen, PIPE
keyconv = "keyconv"
fmt = "num addr" if opts['gen_what'] == ("addrs") else (
"num sec wif" if opts['gen_what'] == ("keys") else "num sec wif addr")
fmt = "num addr" if opts['gen_what'] == ["addrs"] else (
"num sec wif" if opts['gen_what'] == ["keys"] else "num sec wif addr")
from collections import namedtuple
addrinfo = namedtuple("addrinfo",fmt)
@ -78,7 +79,9 @@ def generate_addrs(seed, addrnums, opts):
t_addrs,num,pos,out = len(addrnums),0,0,[]
addrnums.sort() # needed only if caller didn't sort
try:
ws = 'key' if 'keys' in opts['gen_what'] else 'address'
if t_addrs != 1: wp = ws+"s" if ws == 'key' else ws+"es"
while pos != t_addrs:
seed = sha512(seed).digest()
num += 1 # round
@ -88,8 +91,7 @@ def generate_addrs(seed, addrnums, opts):
pos += 1
qmsg_r("\rGenerating %s %s (%s of %s)" %
(opts['gen_what'][-1],num,pos,t_addrs))
qmsg_r("\rGenerating %s #%s (%s of %s)" % (ws,num,pos,t_addrs))
# Secret key is double sha256 of seed hash round /num/
sec = sha256(sha256(seed).digest()).hexdigest()
@ -101,14 +103,7 @@ def generate_addrs(seed, addrnums, opts):
out.append(eval("addrinfo("+addrinfo_args+")"))
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
w = 'key' if 'keys' in opts['gen_what'] else 'address'
if t_addrs != 1: w = w+"s" if w == 'key' else w+"es"
qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
qmsg("\rGenerated %s %s%s"%(t_addrs, wp, " "*15))
return out

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
bitcoin.py: Bitcoin address/key conversion functions
"""

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
config.py: Constants and configuration options for the mmgen suite
config.py: Constants and configuration options for the MMGen suite
"""
import sys, os

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
crypto.py: Cryptographic and related routines for the mmgen-tool utility
crypto.py: Cryptographic and related routines for the 'mmgen-tool' utility
"""
import sys
@ -368,27 +369,30 @@ def _get_seed_from_brain_passphrase(words,opts):
# Vars for mmgen_*crypt functions only
salt_len,sha256_len,nonce_len = 32,32,32
def mmgen_encrypt(data,hash_preset,opts):
def mmgen_encrypt(data,what="data",hash_preset='3',opts={}):
salt,iv,nonce = get_random(salt_len,opts),\
get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
vmsg("Encrypting %s" % what)
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_new_passphrase("passphrase",{})
key = make_key(passwd, salt, hp)
enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key,
int(hexlify(iv),16))
int(hexlify(iv),16), what=what)
return salt+iv+enc_d
def mmgen_decrypt(data,hash_preset,opts):
def mmgen_decrypt(data,what="data",hash_preset='3',opts={}):
dstart = salt_len + g.aesctr_iv_len
salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
vmsg("Preparing to decrypt %s" % what)
qmsg("Using %s hash preset of '%s'" % (m,hp))
passwd = get_mmgen_passphrase("Enter passphrase: ",{})
key = make_key(passwd, salt, hp)
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16))
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what)
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
vmsg("Success. Passphrase and hash preset are correct")
return dec_d[sha256_len+nonce_len:]
else:
msg("Incorrect passphrase or hash preset")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
license.py: Show the license
"""
@ -594,6 +595,7 @@ def do_license_msg(immed=False):
prompt = "%s " % gpl['prompt'].strip()
while True:
from mmgen.util import my_raw_input
reply = get_char(prompt, immed_chars="wc" if immed else "")
if reply == 'w':
from mmgen.term import do_pager

44
mmgen/main.py Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
main.py - Script launcher for the MMGen suite
"""
import sys, termios
from mmgen.util import msg
def main(progname):
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
if progname == "addrgen": import mmgen.main_addrgen
elif progname == "addrimport": import mmgen.main_addrimport
elif progname == "keygen": import mmgen.main_addrgen
elif progname == "passchg": import mmgen.main_passchg
elif progname == "pywallet": import mmgen.main_pywallet
elif progname == "tool": import mmgen.main_tool
elif progname == "txcreate": import mmgen.main_txcreate
elif progname == "txsend": import mmgen.main_txsend
elif progname == "txsign": import mmgen.main_txsign
elif progname == "walletchk": import mmgen.main_walletchk
elif progname == "walletgen": import mmgen.main_walletgen
except KeyboardInterrupt:
msg("\nUser interrupt")
termios.tcsetattr(fd, termios.TCSADRAIN, old)
sys.exit(1)

191
mmgen/main_addrgen.py Executable file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-addrgen: Generate a series or range of addresses from an MMGen
deterministic wallet
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
from mmgen.addr import *
from mmgen.tx import make_addr_data_chksum
what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses"
help_data = {
'prog_name': g.prog_name,
'desc': """Generate a list or range of {} from an {g.proj_name} wallet,
mnemonic, seed or password""".format(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
-c, --save-checksum Save address list checksum to file
-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: {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 {what} to stdout
-v, --verbose Produce more verbose output{}
-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 {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 {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-f, --flat-list Produce a flat list of keys suitable for use with" +
"\n '{}-txsign'".format(g.proj_name.lower()),
"\n-x, --b16 Print secret keys in hexadecimal too"
)
if what == "keys" else ("","","")),
seed_lens=", ".join([str(i) for i in g.seed_lens]),
what=what, g=g
),
'notes': """
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.
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.
For passphrases all combinations of whitespace are equal, and leading and
trailing space are ignored. This permits reading passphrase data from a
multi-line file with free spacing and indentation. This is particularly
convenient for long brainwallet passphrases, for example.
BRAINWALLET NOTE:
As brainwallets require especially strong hashing to thwart dictionary
attacks, the brainwallet hash preset must be specified by the user, using
the 'p' parameter of the '--from-brain' option
The '--from-brain' option also requires the user to specify a seed length
(the 'l' parameter)
For a brainwallet passphrase to always generate the same keys and addresses,
the same 'l' and 'p' parameters to '--from-brain' must be used in all future
invocations with that passphrase
""".format("\n\nBy default, both addresses and secret keys are generated."
if what == "keys" else "")
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'show_hash_presets' in opts: show_hash_presets()
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1 and (
'from_mnemonic' in opts
or 'from_brain' in opts
or 'from_seed' in opts
or 'from_incog_hidden' in opts
):
infile,addr_idx_arg = "",cmd_args[0]
elif len(cmd_args) == 2:
infile,addr_idx_arg = cmd_args
check_infile(infile)
else: usage(help_data)
addr_idxs = parse_address_list(addr_idx_arg)
if not addr_idxs: sys.exit(2)
do_license_msg()
# Interact with user:
if what == "keys" and not g.quiet:
confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
# Generate data:
seed = get_seed_retry(infile,opts)
seed_id = make_chksum_8(seed)
for l in (
('flat_list', 'no_addresses'),
('flat_list', 'b16'),
): warn_incompatible_opts(opts,l)
opts['gen_what'] = \
["addrs"] if what == "addresses" else (
["keys"] if 'no_addresses' in opts else ["addrs","keys"])
addr_data = generate_addrs(seed, addr_idxs, opts)
addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
addr_data_str = format_addr_data(
addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
if 'flat_list' in opts and user_confirm("Encrypt key list?"):
hp = get_hash_preset_from_user('3')
addr_data_str = mmgen_encrypt(addr_data_str,"key list",hp,opts)
enc_ext = "." + g.mmenc_ext
else: enc_ext = ""
# Output data:
if 'stdout' in opts or not sys.stdout.isatty():
if enc_ext and sys.stdout.isatty():
msg("Cannot write encrypted data to screen. Exiting")
sys.exit(2)
c = True if (what == "keys" and not g.quiet and sys.stdout.isatty()) else False
write_to_stdout(addr_data_str,what,c)
else:
confirm_overwrite = False if g.quiet else True
outfile = "%s.%s%s" % (outfile_base, (
g.keylist_ext if 'flat_list' in opts else (
g.keyfile_ext if opts['gen_what'] == ("keys") else (
g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys"))), enc_ext)
write_to_file(outfile,addr_data_str,opts,what,confirm_overwrite,True)
if 'addrs' in opts['gen_what']:
msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum))
if 'save_checksum' in opts:
a = "address data checksum"
write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,False,True)
else:
qmsg("This checksum will be used to verify the address file in the future.")
qmsg("Record it to a safe location.")

140
mmgen/main_addrimport.py Executable file
View file

@ -0,0 +1,140 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-addrimport: Import addresses into a MMGen bitcoind watching wallet
"""
import sys
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.tx import connect_to_bitcoind,parse_addrs_file
help_data = {
'prog_name': g.prog_name,
'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind
watching wallet""".format(pnm=g.proj_name),
'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'
-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.
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
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)
if cmd_args:
check_infile(cmd_args[0])
seed_id,addr_data = parse_addrs_file(cmd_args[0])
else:
seed_id,addr_data = "",[]
if 'addrlist' in opts:
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
trim_comments=True)
addr_data += [(None,l) for l in lines]
from mmgen.bitcoin import verify_addr
qmsg_r("Validating addresses...")
for i in addr_data:
if not verify_addr(i[1],verbose=True):
msg("%s: invalid address" % i)
sys.exit(2)
qmsg("OK")
import mmgen.config as g
g.http_timeout = 3600
c = connect_to_bitcoind()
m = """
WARNING: You've chosen the '--rescan' option. Rescanning the block chain is
necessary only if an address you're importing is already on the block chain
and has a balance. Note that the rescanning process is very slow (>30 min.
for each imported address on a low-powered computer).
""".strip() if "rescan" in opts else """
WARNING: If any of the addresses you're importing is already on the block chain
and has a balance, you must exit the program now and rerun it using the
'--rescan' option. Otherwise you may ignore this message and continue.
""".strip()
if g.quiet: m = ""
confirm_or_exit(m, "continue", expect="YES")
err_flag = False
def import_address(addr,label,rescan):
try:
c.importaddress(addr,label,rescan)
except:
global err_flag
err_flag = True
w1 = len(str(len(addr_data))) * 2 + 2
w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
if "rescan" in opts:
import threading
import time
msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s"
else:
msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s"
msg("Importing addresses")
for n,i in enumerate(addr_data):
if i[0]:
comment = " " + i[2] if len(i) == 3 else ""
label = "%s:%s%s" % (seed_id,i[0],comment)
else: label = "non-mmgen"
if "rescan" in opts:
t = threading.Thread(target=import_address, args=(i[1],label,True))
t.daemon = True
t.start()
start = int(time.time())
while True:
if t.is_alive():
elapsed = int(time.time() - start)
msg_r(msg_fmt % (
secs_to_hms(elapsed),
("%s/%s:" % (n+1,len(addr_data))),
i[1], "(" + label + ")"
)
)
time.sleep(1)
else:
if err_flag: msg("\nImport failed"); sys.exit(2)
msg("\nOK")
break
else:
import_address(i[1],label,rescan=False)
msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
i[1], "(" + label + ")"))
if err_flag: msg("\nImport failed"); sys.exit(2)
msg(" - OK")

127
mmgen/main_passchg.py Executable file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or
hash preset
"""
import sys
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import *
import mmgen.config as g
help_data = {
'prog_name': g.prog_name,
'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
-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: '{g.hash_preset}')
-P, --passwd-file= f Get new passphrase from file 'f'
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-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
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'show_hash_presets' in opts: show_hash_presets()
if len(cmd_args) != 1:
msg("One input file must be specified")
sys.exit(2)
infile = cmd_args[0]
# Old key:
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
seed_id,key_id = metadata[:2]
# Repeat on incorrect pw entry
prompt = "Enter %spassphrase: " % (""
if 'keep_old_passphrase' in opts else "old ")
while True:
passwd = get_mmgen_passphrase(prompt,{})
key = make_key(passwd, salt, hash_preset)
seed = decrypt_seed(enc_seed, key, seed_id, key_id)
if seed: break
changed = {}
if 'label' in opts:
if opts['label'] != label:
msg("Label changed: '%s' -> '%s'" % (label, opts['label']))
changed['label'] = True
else:
msg("Label is unchanged: '%s'" % (label))
else: opts['label'] = label # Copy the old label
if 'hash_preset' in opts:
if hash_preset != opts['hash_preset']:
qmsg("Hash preset has changed (%s -> %s)" %
(hash_preset, opts['hash_preset']))
changed['preset'] = True
else:
msg("Hash preset is unchanged")
else:
opts['hash_preset'] = hash_preset
if 'keep_old_passphrase' in opts:
msg("Keeping old passphrase by user request")
else:
new_passwd = get_new_passphrase("new passphrase", opts)
if new_passwd == passwd:
qmsg("Passphrase is unchanged")
else:
qmsg("Passphrase has changed")
passwd = new_passwd
changed['passwd'] = True
if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
qmsg("Will update salt and key ID")
from hashlib import sha256
salt = sha256(salt + get_random(128,opts)).digest()[:g.salt_len]
key = make_key(passwd, salt, opts['hash_preset'])
new_key_id = make_chksum_8(key)
qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
key_id = new_key_id
enc_seed = encrypt_seed(seed, key)
elif not 'label' in changed:
msg("Data unchanged. No file will be written")
sys.exit(2)
write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts)

1677
mmgen/main_pywallet.py Executable file

File diff suppressed because it is too large Load diff

70
mmgen/main_tool.py Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-tool: Perform various Bitcoin-related operations.
Part of the MMGen suite
"""
import sys
import mmgen.config as g
import mmgen.tool as tool
from mmgen.Opts import *
help_data = {
'prog_name': g.prog_name,
'desc': "Perform various BTC-related operations",
'usage': "[opts] <command> <command args>",
'options': """
-d, --outdir= d Specify an alternate directory 'd' for output
-h, --help Print this help message
-q, --quiet Produce quieter output
-r, --usr-randchars=n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-v, --verbose Produce more verbose output
""".format(g=g),
'notes': """
COMMANDS:{}
Type '{} <command> --help for usage information on a particular
command
""".format(tool.command_help,g.prog_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if len(cmd_args) < 1:
usage(help_data)
sys.exit(1)
command = cmd_args.pop(0)
if command not in tool.commands.keys():
msg("'%s': No such command" % command)
sys.exit(1)
if cmd_args and cmd_args[0] == '--help':
tool.tool_usage(g.prog_name, command)
sys.exit(0)
args = tool.process_args(g.prog_name, command, cmd_args)
tool.opts = opts
#print command + "(" + ", ".join(args) + ")"
eval("tool." + command + "(" + ", ".join(args) + ")")

221
mmgen/main_txcreate.py Executable file
View file

@ -0,0 +1,221 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs
to MMGen- or non-MMGen outputs
"""
import sys
from decimal import Decimal
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg, msg_r, user_confirm
help_data = {
'prog_name': g.prog_name,
'desc': "Create a BTC transaction with outputs to specified addresses",
'usage': "[opts] <addr,amt> ... [change addr] [addr file] ...",
'options': """
-h, --help Print this help message
-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
""".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 {g.mins_per_block} minutes.
Addresses on the command line can be Bitcoin addresses or {pnm} 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.
""".format(g=g,pnm=g.proj_name)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
c = connect_to_bitcoind()
if not 'info' in opts:
do_license_msg(immed=True)
tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
cmd_args = set(cmd_args) - set(addrfiles)
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:
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 cmd_args:
if "," in a:
a1,a2 = a.split(",")
if is_btc_addr(a1):
btcaddr = a1
elif is_mmgen_addr(a1):
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] = normalize_btc_amt(a2)
else:
msg("%s: invalid amount in argument '%s'" % (a2,a))
sys.exit(2)
elif is_mmgen_addr(a) or is_btc_addr(a):
if change_addr:
msg("ERROR: More than one change address specified: %s, %s" %
(change_addr, a))
sys.exit(2)
change_addr = a if is_btc_addr(a) else \
mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map)
tx_out[change_addr] = 0
else:
msg("%s: unrecognized argument" % a)
sys.exit(2)
if not tx_out:
msg("At least one output must be specified on the command line")
sys.exit(2)
tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.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)
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
#if False:
if g.bogus_wallet_data:
import mmgen.rpc
us = eval(get_data_from_file(g.bogus_wallet_data))
else:
us = c.listunspent()
if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
unspent = sort_and_view(us,opts)
total = trim_exponent(sum([i.amount for i in unspent]))
msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
if 'info' in opts: sys.exit(0)
send_amt = sum([tx_out[i] for i in tx_out.keys()])
msg("Total amount to spend: %s%s" % (
(send_amt or "Unknown")," BTC" if send_amt else ""))
while True:
sel_nums = select_outputs(unspent,
"Enter a range or space-separated list of outputs to spend: ")
msg("Selected output%s: %s" %
(("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums))
)
sel_unspent = [unspent[i-1] for i in sel_nums]
mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
mmaddrs.discard("")
if mmaddrs and len(mmaddrs) < len(sel_unspent):
msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
if not user_confirm("Accept?"):
continue
total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
change = trim_exponent(total_in - (send_amt + tx_fee))
if change >= 0:
prompt = "Transaction produces %s BTC in change. OK?" % change
if user_confirm(prompt,default_yes=True):
break
else:
msg(txmsg['not_enough_btc'] % change)
if change > 0 and not change_addr:
msg(txmsg['throwaway_change'] % change)
sys.exit(2)
if change_addr in tx_out and not change:
msg("Warning: Change address will be unused as transaction produces no change")
del tx_out[change_addr]
for k,v in tx_out.items(): tx_out[k] = float(v)
if change > 0: tx_out[change_addr] = float(change)
tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
if g.debug:
print "tx_in:", repr(tx_in)
print "tx_out:", repr(tx_out)
tx_hex = c.createrawtransaction(tx_in,tx_out)
qmsg("Transaction successfully created")
prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
pager = True if reply in "Vv" else False
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
prompt = "Save transaction?"
if user_confirm(prompt,default_yes=True):
amt = send_amt or change
tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext)
data = "{} {} {}\n{}\n{}\n{}\n".format(
tx_id, amt, make_timestamp(),
tx_hex,
repr([i.__dict__ for i in sel_unspent]),
repr(b2m_map)
)
write_to_file(outfile,data,opts,"transaction",False,True)
else:
msg("Transaction not saved")

85
mmgen/main_txsend.py Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit
help_data = {
'prog_name': g.prog_name,
'desc': "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()),
'usage': "[opts] <signed transaction file>",
'options': """
-h, --help Print this help message
-d, --outdir= d Specify an alternate directory 'd' for output
-q, --quiet Suppress warnings; overwrite files without prompting
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if len(cmd_args) == 1:
infile = cmd_args[0]; check_infile(infile)
else: usage(help_data)
# Begin execution
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"
if g.quiet: warn,expect = "","YES"
confirm_or_exit(warn, what, expect)
msg("Sending transaction")
try:
tx_id = c.sendrawtransaction(tx_hex)
except:
msg("Unable to send transaction")
sys.exit(3)
msg("Transaction sent: %s" % tx_id)
of = "tx_{}[{}].out".format(*metadata[:2])
write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True)

213
mmgen/main_txsign.py Executable file
View file

@ -0,0 +1,213 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.tx import *
from mmgen.util import msg,qmsg
help_data = {
'prog_name': g.prog_name,
'desc': "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()),
'usage': "[opts] <transaction file> .. [mmgen wallet/seed/words/brainwallet file] .. [addrfile] ..",
'options': """
-h, --help Print this help message
-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= f Provide additional keys for non-{pnm} addresses
-K, --all-keys-from-file=f Like '-k', only use the keyfile as key source
for ALL inputs, including {pnm} ones. Can be used
for online signing without an {pnm} seed source.
{pnm}-to-BTC mappings can optionally be verified
using address file(s) listed on the command line
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-v, --verbose Produce more verbose output
-V, --skip-key-preverify Skip optional key pre-verification step
-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
'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 .{g.seed_ext} format
""".format(g=g,pnm=g.proj_name),
'notes': """
Transactions with either {pnm} or non-{pnm} input addresses may be signed.
For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source.
For {pnm} inputs, key data is generated from your seed as with the
{pnl}-addrgen and {pnl}-keygen utilities.
Data for the --from-<what> options will be taken from a file if a second
file is specified on the command line. Otherwise, the user will be
prompted to enter the data.
In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm}
keys must be supplied in a separate file (WIF format, one key per line)
using the '--keys-from-file' option. Alternatively, one may get keys from
a running bitcoind using the '--force-wallet-dat' option. First import the
required {pnm} keys using 'bitcoind importprivkey'.
For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address
mappings are verified. Therefore, seed material for these addresses must
be supplied on the command line (but see '--all-keys-from-file').
Seed data supplied in files must have the following extensions:
wallet: '.{g.wallet_ext}'
seed: '.{g.seed_ext}'
mnemonic: '.{g.mn_ext}'
brainwallet: '.{g.brain_ext}'
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower())
}
opts,infiles = parse_opts(sys.argv,help_data)
for l in (
('tx_id', 'info'),
('keys_from_file','all_keys_from_file')
): warn_incompatible_opts(opts,l)
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
opts['from_incog'] = True
if 'all_keys_from_file' in opts:
opts['keys_from_file'] = opts['all_keys_from_file']
# opts['skip_key_preverify'] = True
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
c = connect_to_bitcoind()
saved_seeds = {}
tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext]
infiles = list(set(infiles) - set(tx_files) - set(addrfiles))
if not "info" in opts: do_license_msg(immed=True)
if 'keys_from_file' in opts:
from mmgen.crypto import mmgen_decrypt
fn = opts['keys_from_file']
d = get_data_from_file(fn,"keylist")
if get_extension(fn) == g.mmenc_ext or not \
is_b58_str(remove_comments(d.split("\n"))[0][:55]):
qmsg("Keylist appears to be encrypted")
while True:
hp = get_hash_preset_from_user('3')
d_dec = mmgen_decrypt(d,"encrypted keylist",hp,opts)
if d_dec: d = d_dec; break
else: msg("Trying again...")
keys_from_file = remove_comments(d.split("\n"))
else: keys_from_file = []
for tx_file in tx_files:
m = "" if 'tx_id' in opts else "transaction data"
tx_data = get_lines_from_file(tx_file,m)
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
qmsg("Successfully opened transaction file '%s'" % tx_file)
if 'tx_id' in opts:
msg(metadata[0])
sys.exit(0)
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
sys.exit(0)
# Are inputs mmgen addresses?
mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
if 'all_keys_from_file' in opts: other_inputs = inputs_data
keys = keys_from_file
if other_inputs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_inputs)
sys.exit(2)
if other_inputs and keys and not 'skip_key_preverify' in opts:
a = [i['address'] for i in other_inputs]
preverify_keys(a, keys)
opts['skip_key_preverify'] = True
if 'all_keys_from_file' in opts:
if addrfiles:
check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles)
else:
confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue")
else:
check_mmgen_to_btc_addr_mappings(
mmgen_inputs,b2m_map,infiles,saved_seeds,opts)
if len(tx_files) > 1:
msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files)))
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
if reply and reply in "YyVv":
p = True if reply in "Vv" else False
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
sig_data = [
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
for i in inputs_data]
if mmgen_inputs and not 'all_keys_from_file' in opts:
ml = [i['account'].split()[0] for i in mmgen_inputs]
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)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_inputs:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
if sig_tx['complete']:
prompt = "OK\nSave signed transaction?"
if user_confirm(prompt,default_yes=True):
outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
data = "{}\n{}\n{}\n{}\n".format(
" ".join(metadata[:2] + [make_timestamp()]),
sig_tx['hex'],
repr(inputs_data),
repr(b2m_map)
)
write_to_file(outfile,data,opts,"signed transaction",True,True)
else:
msg("failed\nSome keys were missing. Transaction could not be signed.")
sys.exit(3)

115
mmgen/main_walletchk.py Executable file
View file

@ -0,0 +1,115 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display
information about it and export it to various formats
"""
import sys
import mmgen.config as g
from mmgen.Opts import *
from mmgen.util import *
from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
help_data = {
'prog_name': g.prog_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
-e, --echo-passphrase Print passphrase to screen when typing it
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-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'
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
""".format(g=g),
'notes': """
Since good randomness is particularly important for incognito wallets,
the '--usr-randchars' option is turned on by default to gather additional
entropy from the user when one of the '--export-incog*' options is
selected. If you fully trust your OS's random number generator and wish
to disable this option, then specify '-r0' on the command line.
"""
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'export_incog_hidden' in opts or 'export_incog_hex' in opts:
opts['export_incog'] = True
if len(cmd_args) != 1: usage(help_data)
check_infile(cmd_args[0])
if 'export_mnemonic' in opts:
qmsg("Exporting mnemonic data to file by user request")
elif 'export_seed' in opts:
qmsg("Exporting seed data to file by user request")
elif 'export_incog' in opts:
if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
qmsg("Exporting wallet to incognito format by user request")
incog_enc,seed_id,key_id,iv_id,preset = \
wallet_to_incog_data(cmd_args[0],opts)
if "export_incog_hidden" in opts:
export_to_hidden_incog(incog_enc,opts)
else:
seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8
fn = "%s-%s-%s[%s,%s].%s" % (
seed_id, key_id, iv_id, seed_len, preset,
g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext
)
data = pretty_hexdump(incog_enc,2,8,line_nums=False) \
if "export_incog_hex" in opts else incog_enc
export_to_file(fn, data, opts, "incognito wallet data")
sys.exit()
seed = get_seed_from_wallet(cmd_args[0], opts)
if seed: qmsg("Wallet is OK")
else:
msg("Error opening wallet")
sys.exit(2)
if 'export_mnemonic' in opts:
wl = get_default_wordlist()
from mmgen.mnemonic import get_mnemonic_from_seed
p = True if g.debug else False
mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data")
elif 'export_seed' in opts:
from mmgen.bitcoin import b58encode_pad
data = col4(b58encode_pad(seed))
chk = make_chksum_6(b58encode_pad(seed))
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext)
export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data")

149
mmgen/main_walletgen.py Executable file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mmgen-walletgen: Generate an MMGen deterministic wallet
"""
import sys, os
from hashlib import sha256
import mmgen.config as g
from mmgen.Opts import *
from mmgen.license import *
from mmgen.util import *
from mmgen.crypto import *
help_data = {
'prog_name': g.prog_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
-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: {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: '{g.hash_preset}')
-P, --passwd-file= f Get passphrase from file 'f'
-q, --quiet Produce quieter output; overwrite files without
prompting
-r, --usr-randchars= n Get 'n' characters of additional randomness from
user (min={g.min_urandchars}, max={g.max_urandchars})
-v, --verbose Produce more verbose output
-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 .{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),
{g.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.
For passphrases all combinations of whitespace are equal, and leading and
trailing space are ignored. This permits reading passphrase data from a
multi-line file with free spacing and indentation. This is particularly
convenient for long brainwallet passphrases, for example.
Since good randomness is particularly important when generating wallets,
the '--usr-randchars' option is turned on by default to gather additional
entropy from the user. If you fully trust your OS's random number gener-
ator and wish to disable this option, specify '-r0' on the command line.
BRAINWALLET NOTE:
As brainwallets require especially strong hashing to thwart dictionary
attacks, the brainwallet hash preset must be specified by the user, using
the 'p' parameter of the '--from-brain' option. This preset should be
stronger than the one used for hashing the seed (i.e. the default value or
the one specified in the '--hash-preset' option).
The '--from-brain' option also requires the user to specify a seed length
(the 'l' parameter), which overrides both the default and any one given in
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(g=g)
}
opts,cmd_args = parse_opts(sys.argv,help_data)
if 'show_hash_presets' in opts: show_hash_presets()
if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
if len(cmd_args) == 1:
infile = cmd_args[0]
check_infile(infile)
ext = infile.split(".")[-1]
ok_exts = g.seedfile_exts
for e in ok_exts:
if e == ext: break
else:
msg(
"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
sys.exit(1)
elif len(cmd_args) == 0:
infile = ""
else: usage(help_data)
# Begin execution
do_license_msg()
if 'from_brain' in opts and not g.quiet:
confirm_or_exit(cmessages['brain_warning'].format(
g.proj_name, *get_from_brain_opt_params(opts)),
"continue")
for i in 'from_mnemonic','from_brain','from_seed','from_incog':
if infile or (i in opts):
seed = get_seed_retry(infile,opts)
if "from_incog" in opts or get_extension(infile) == g.incog_ext:
qmsg(cmessages['incog'] % make_chksum_8(seed))
else: qmsg("")
break
else:
# Truncate random data for smaller seed lengths
seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8]
salt = sha256(get_random(128,opts)).digest()[:g.salt_len]
qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset'])
passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
key = make_key(passwd, salt, opts['hash_preset'])
enc_seed = encrypt_seed(seed, key)
write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)

View file

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

View file

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

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
mnemonic.py: Mnemomic routines for the mmgen suite
mnemonic.py: Mnemomic routines for the MMGen suite
"""
wl_checksums = {

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
term.py: Terminal-handling routines for the mmgen suite
term.py: Terminal-handling routines for the MMGen suite
"""
import sys, os, struct
@ -30,16 +31,12 @@ def _kb_hold_protect_unix():
timeout = float(0.3)
try:
while True:
key = select([sys.stdin], [], [], timeout)[0]
if key: sys.stdin.read(1)
else: break
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
finally:
else:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
break
def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
@ -51,33 +48,27 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True):
old = termios.tcgetattr(fd)
tty.setcbreak(fd)
try:
while True:
# Protect against held-down key before read()
key = select([sys.stdin], [], [], timeout)[0]
ch = sys.stdin.read(1)
if prehold_protect:
if key: continue
if immed_chars == "ALL" or ch in immed_chars:
return ch
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
return ch
if immed_chars == "ALL" or ch in immed_chars: break
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": break
# Protect against long keypress
key = select([sys.stdin], [], [], timeout)[0]
if key: continue
else: return ch
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
finally:
if not key: break
termios.tcsetattr(fd, termios.TCSADRAIN, old)
return ch
def _kb_hold_protect_mswin():
timeout = float(0.5)
try:
while True:
hit_time = time.time()
while True:
@ -86,9 +77,6 @@ def _kb_hold_protect_mswin():
break
if float(time.time() - hit_time) > timeout:
return
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
@ -96,7 +84,6 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
msg_r(prompt)
timeout = float(0.5)
try:
while True:
if msvcrt.kbhit():
ch = msvcrt.getch()
@ -114,9 +101,6 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True):
if msvcrt.kbhit(): break
if float(time.time() - hit_time) > timeout:
return ch
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
def _get_terminal_size_linux():
@ -221,13 +205,7 @@ def do_pager(text):
p = Popen([pager], stdin=PIPE, shell=shell)
except: pass
else:
try:
p.communicate(text+end+"\n")
except KeyboardInterrupt:
# Has no effect. Why?
if pager != "less":
msg("\n(User interrupt)\n")
finally:
msg_r("\r")
break
else: print text+end

View file

@ -38,7 +38,6 @@ def b58_randenc():
sys.exit(9)
def keyconv_compare_randloop(loops, quiet=False):
try:
for i in range(1,int(loops)+1):
@ -56,8 +55,6 @@ def keyconv_compare_randloop(loops, quiet=False):
else:
print "%s iterations completed" % i
except KeyboardInterrupt:
msg("\nUser interrupt")
def keyconv_compare(wif,quiet=False):
do_msg = nomsg if quiet else msg
@ -144,7 +141,6 @@ def b58tohex_pad(s_in, quiet=False):
b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet)
def hextob58_pad_randloop(loops, quiet=False):
try:
for i in range(1,int(loops)+1):
r = hexlify(get_random(32))
hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
@ -153,8 +149,6 @@ def hextob58_pad_randloop(loops, quiet=False):
sys.stderr.write("\riteration: %i " % i)
sys.stderr.write("\r%s iterations completed\n" % i)
except KeyboardInterrupt:
msg("\nUser interrupt")
def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif):
print "Input: %s" % s_in

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
tool.py: Routines and data for the mmgen-tool utility
tool.py: Routines and data for the 'mmgen-tool' utility
"""
import sys
@ -415,9 +416,9 @@ def hex2wif(hexpriv,compressed=False):
print bitcoin.hextowif(hexpriv,compressed)
def encrypt(infile,outfile="",hash_preset=''):
def encrypt(infile,outfile="",hash_preset='3'):
data = get_data_from_file(infile,"data for encryption")
enc_d = mmgen_encrypt(data,hash_preset,opts)
enc_d = mmgen_encrypt(data,"",hash_preset,opts)
if outfile == '-':
write_to_stdout(enc_d,"encrypted data",confirm=True)
else:
@ -426,9 +427,9 @@ def encrypt(infile,outfile="",hash_preset=''):
write_to_file(outfile, enc_d, opts,"encrypted data",True,True)
def decrypt(infile,outfile="",hash_preset=''):
def decrypt(infile,outfile="",hash_preset='3'):
enc_d = get_data_from_file(infile,"encrypted data")
dec_d = mmgen_decrypt(enc_d,hash_preset,opts)
dec_d = mmgen_decrypt(enc_d,"",hash_preset,opts)
if outfile == '-':
write_to_stdout(dec_d,"decrypted data",confirm=True)
else:

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
tx.py: Bitcoin transaction routines
"""
@ -453,6 +454,12 @@ def is_btc_addr(s):
from mmgen.bitcoin import verify_addr
return verify_addr(s)
def is_b58_str(s):
from mmgen.bitcoin import b58a
for ch in s:
if ch not in b58a: return False
return True
def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
@ -720,8 +727,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,s))
msg("No keys found for the following address%s:" % s)
print " %s" % "\n ".join(addrs)
sys.exit(2)

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
# Copyright (C)2013-2014 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
util.py: Low-level routines imported by other modules for the MMGen suite
"""
@ -342,11 +343,11 @@ def write_to_stdout(data, what, confirm=True):
sys.stdout.write(data)
def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False):
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
if confirm:
if confirm_overwrite:
from os import stat
try:
stat(outfile)
@ -370,11 +371,9 @@ def export_to_file(outfile, data, opts, what="data"):
if 'stdout' in opts:
write_to_stdout(data, what, confirm=True)
elif not sys.stdout.isatty():
write_to_stdout(data, what, confirm=False)
else:
c = False if g.quiet else True
write_to_file(outfile,data,opts,what,c,True)
confirm_overwrite = False if g.quiet else True
write_to_file(outfile,data,opts,what,confirm_overwrite,True)
from mmgen.bitcoin import b58decode_pad,b58encode_pad
@ -430,9 +429,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
outfile="{}-{}[{},{}].{}".format(
seed_id,key_id,seed_len,hash_preset,g.wallet_ext)
c = False if g.quiet else True
d = "\n".join((chk,)+lines)+"\n"
write_to_file(outfile,d,opts,"wallet",c,True)
confirm_overwrite = False if g.quiet else True
write_to_file(outfile,d,opts,"wallet",confirm_overwrite,True)
if g.verbose:
display_control_data(label,metadata,hash_preset,salt,enc_seed)
@ -684,22 +683,29 @@ def export_to_hidden_incog(incog_enc,opts):
msg("Data written to file '%s' at offset %s" %
(os.path.relpath(outfile),offset))
from mmgen.term import kb_hold_protect,get_char
def get_hash_preset_from_user(hp='3'):
p = "Enter hash preset, or hit ENTER to accept the default ('%s'): " % hp
while True:
ret = my_raw_input(p)
if ret:
if ret in g.hash_presets.keys(): return ret
else:
msg("Invalid input. Valid choices are %s" %
", ".join(sorted(g.hash_presets.keys())))
continue
else: return hp
def my_raw_input(prompt,echo=True):
msg_r(prompt)
kb_hold_protect()
try:
if echo:
reply = raw_input("")
else:
from getpass import getpass
reply = getpass("")
except KeyboardInterrupt:
msg("\nUser interrupt")
sys.exit(1)
kb_hold_protect()
return reply