Windows port fully functional; fixes, enhancements

This commit is contained in:
philemon 2014-03-22 20:39:54 +04:00
commit dcab2602b6
19 changed files with 477 additions and 358 deletions

View file

@ -44,20 +44,22 @@ else: extra_help_data = ("","","")
help_data = {
'prog_name': sys.argv[0].split("/")[-1],
'desc': """Generate a list or range of {} from a {} wallet,
mnemonic, seed or password""".format(gen_what,proj_name),
mnemonic, seed or password""".format(gen_what,proj_name),
'usage':"[opts] [infile] <address list>",
'options': """
-h, --help Print this help message{}
-d, --outdir d Specify an alternate directory 'd' for output
-e, --echo-passphrase Display passphrase or mnemonic on screen upon entry
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
-H, --show-hash-presets Show information on available hash presets
-K, --no-keyconv Use internal libraries for address generation
instead of 'keyconv'
-l, --seed-len N Length of seed. Options: {}
(default: {})
-p, --hash-preset p Use scrypt.hash() parameters from preset 'p'
when hashing password (default: '{}')
-P, --show-hash-presets Show information on available hash presets
-q, --quiet Suppress warnings; overwrite files without asking
-P, --passwd-file f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-S, --stdout Print {W} to stdout
-v, --verbose Produce more verbose output{}
@ -90,9 +92,9 @@ 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
For a brainwallet passphrase to always generate the same keys and
addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
in all future invocations with that passphrase
""".format(
extra_help_data[0],
", ".join([str(i) for i in seed_lens]),
@ -105,16 +107,16 @@ invocations with that passphrase
)
}
so = "h","A","d:","e","K","l:","p:","P","q","S","v","x","b:","m","s"
lo = "help","no_addresses","outdir=","echo_passphrase","no_keyconv",\
"seed_len=","hash_preset=","show_hash_presets","quiet","stdout",\
"verbose","b16","from_brain=","from_mnemonic","from_seed"
short_opts = ["h","A","d:","e","H","K","l:","p:","P:","q","S",
"v","x","b:","m","s"]
long_opts = ["help","no_addresses","outdir=","echo_passphrase",
"show_hash_presets","no_keyconv","seed_len=","hash_preset=",
"passwd_file=","quiet","stdout","verbose","b16","from_brain=",
"from_mnemonic","from_seed"]
if invoked_as == "addrgen":
short_opts = so[0:1] + so[2:10] + so[11:]
long_opts = lo[0:1] + lo[2:10] + lo[11:]
else:
short_opts,long_opts = so,lo
for i in "A","x": short_opts.remove(i)
for i in "no_addresses","b16": long_opts.remove(i)
opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
@ -132,10 +134,6 @@ set_if_unset_and_typeconvert(opts,(
# Exits on invalid input
check_opts(opts,('hash_preset','seed_len','outdir','from_brain'))
if debug:
print "Processed options: %s" % repr(opts)
print "Cmd args: %s" % repr(cmd_args)
if len(cmd_args) == 1 and (
'from_mnemonic' in opts or
'from_brain' in opts or

View file

@ -33,9 +33,9 @@ help_data = {
watching wallet""",
'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
-h, --help Print this help message
-l, --addrlist f Import the non-mmgen Bitcoin addresses listed in file 'f'
-q, --quiet Suppress warnings
"""
}

View file

@ -33,12 +33,13 @@ help_data = {
'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: '{}')
-P, --show-hash-presets Show information on available hash presets
-P, --passwd-file f Get new passphrase from file 'f'
-v, --verbose Produce more verbose output
NOTE: The key ID will change if either the passphrase or hash preset
@ -46,9 +47,9 @@ NOTE: The key ID will change if either the passphrase or hash preset
""".format(hash_preset)
}
short_opts = "hd:kL:p:Pv"
long_opts = "help","outdir=","keep_old_passphrase","label=","hash_preset=",\
"show_hash_presets","verbose"
short_opts = "hd:HkL:p:P:v"
long_opts = "help","outdir=","show_hash_presets","keep_old_passphrase",\
"label=","hash_preset=","passwd_file=","verbose"
opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
@ -65,11 +66,12 @@ infile = cmd_args[0]
# Old key:
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
seed_id,key_id = metadata[:2]
oldp = "" if 'keep_old_passphrase' in opts else "old "
# Repeat on incorrect pw entry
prompt = "Enter %spassphrase: " % (""
if 'keep_old_passphrase' in opts else "old ")
while True:
passwd = " ".join(get_words("","",("Enter %spassphrase: " % oldp),opts))
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
@ -97,8 +99,7 @@ else:
if 'keep_old_passphrase' in opts:
msg("Keeping old passphrase by user request")
else:
new_passwd = get_first_passphrase_from_user(
"new passphrase", {'quiet': True })
new_passwd = get_new_passphrase("new passphrase", opts)
if new_passwd == passwd:
msg("Passphrase is unchanged")

View file

@ -46,7 +46,7 @@ mmgen-pywallet: Dump contents of a bitcoind wallet to file
from mmgen.Opts import *
from mmgen.utils import msg
from bsddb.db import *
import os, sys, time
import sys, time
import json
import logging
import struct
@ -75,17 +75,20 @@ help_data = {
'options': """
-h, --help Print this help message
-d, --outdir d Specify an alternate directory 'd' for output
-e, --echo-passphrase Display passphrase on screen upon entry
-j, --json Dump wallet in json format
-k, --keys Dump all private keys (flat list)
-a, --addrs Dump all addresses (flat list)
-K, --keysforaddrs f Dump private keys for addresses listed in file 'f'
-q, --quiet Suppress warnings; overwrite files without asking
-P, --passwd-file f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-S, --stdout Dump to stdout rather than file
"""
}
short_opts = "hd:jkaK:qS"
long_opts = "help","outdir=","json","keys","addrs","keysforaddrs=","quiet","stdout"
short_opts = "hd:ejkaK:P:qS"
long_opts = "help","outdir=","echo_passphrase","json","keys","addrs",\
"keysforaddrs=","passwd_file=","quiet","stdout"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
@ -1572,8 +1575,8 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction
if password == None and \
('json' in opts or 'keysforaddrs' in opts or 'keys' in opts):
from mmgen.utils import my_getpass
password = my_getpass("Enter password: ")
from mmgen.utils import get_bitcoind_passphrase
password = get_bitcoind_passphrase("Enter password: ",opts)
if password != None:
global crypter
@ -1646,21 +1649,14 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction
del(json_db['names'])
def parse_wallet_file_arg(path):
s = path.rfind("/")
if path[0] == '/':
if s == 0: return "/", path
else: return path[0:s], path[s+1:]
else:
curdir = os.path.abspath(".")
if s == -1: return curdir,path
else: return curdir + "/" + path[0:s], path[s+1:]
# Non-portable. For Windows, works only if supplied filename is in current dir
# main()
db_dir,db_file = parse_wallet_file_arg(cmd_args[0])
import os.path
infile = os.path.abspath(cmd_args[0])
db_dir,db_file = os.path.dirname(infile),os.path.basename(infile)
# print "[%s] [%s]" % (db_dir,db_file)
db_env = create_env(db_dir)
@ -1673,18 +1669,16 @@ if json_db.get('minversion') > max_version:
wallet_addrs = [i['addr'] for i in json_db['keys']]
data,ext,what = [],"",""
if 'json' in opts:
data = [ json.dumps(json_db, sort_keys=True, indent=4) ]
data = [json.dumps(json_db, sort_keys=True, indent=4)]
ext,what = "json","json dump"
elif 'keys' in opts:
for i in json_db['keys']: data.append(i['sec'])
data = sorted([i['sec'] for i in json_db['keys']])
ext,what = "keys","private keys"
elif 'addrs' in opts:
for i in json_db['keys']: data.append(i['addr'])
data = sorted([i['addr'] for i in json_db['keys']])
ext,what = "addrs","addresses"
elif 'keysforaddrs' in opts:
@ -1696,7 +1690,7 @@ elif 'keysforaddrs' in opts:
data.append(json_db['keys'][idx]['sec'])
except:
msg("WARNING: Address '%s' not found" % addr)
ext,what = "keys","private keys"
data,ext,what = sorted(data),"keys","private keys"
len_arg = "%s" % len(wallet_addrs) \
if len(data) == len(wallet_addrs) or ext == "json" \

View file

@ -26,7 +26,7 @@ from mmgen.Opts import *
from mmgen.license import *
from mmgen.config import *
from mmgen.tx import *
from mmgen.utils import check_opts, msg, user_confirm
from mmgen.utils import check_opts, msg, msg_r, user_confirm
from decimal import Decimal
prog_name = sys.argv[0].split("/")[-1]
@ -40,12 +40,13 @@ help_data = {
-d, --outdir d Specify an alternate directory 'd' for output
-e, --echo-passphrase Print passphrase to screen when typing it
-i, --info Display unspent outputs and exit
-q, --quiet Suppress warnings; overwrite files without asking
-q, --quiet Suppress warnings; overwrite files without
prompting
Outputs to spend are chosen by the user via a menu.
Ages of transactions are approximate based on an estimated block discovery
time of %s minutes.
Ages of transactions are approximate based on an average block discovery
interval of %s minutes.
""" % mins_per_block
}
@ -88,21 +89,29 @@ if not 'quiet' in opts and not 'info' in opts: do_license_msg()
# End test
us = c.listunspent()
if not us:
msg_r("""
No spendable outputs found! Import addresses with balances into your
watch-only wallet using 'mmgen-addrimport' and then re-run this program.
""")
sys.exit(2)
# write_to_file("listunspent.json",repr(us))
# sys.exit()
unspent = sort_and_view(us)
total = trim_exponent(sum([i.amount for i in unspent]))
msg("Total unspent: %s BTC" % total)
msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
if 'info' in opts: sys.exit(0)
send_amt = sum(tx_out.values())
msg("Total amount to spend: %s BTC\n%s unspent outputs total" %
(send_amt, len(unspent)))
msg("Total amount to spend: %s BTC" % send_amt)
while True:
sel_nums = select_outputs(unspent,"Choose the outputs to spend: ")
sel_nums = select_outputs(unspent,
"Enter a range or space-separated list of outputs to spend: ")
msg("Selected outputs: %s" % " ".join(str(i) for i in sel_nums))
sel_unspent = [unspent[i-1] for i in sel_nums]

View file

@ -36,7 +36,7 @@ help_data = {
'options': """
-h, --help Print this help message
-d, --outdir d Specify an alternate directory 'd' for output
-q, --quiet Suppress warnings; overwrite files without asking
-q, --quiet Suppress warnings; overwrite files without prompting
"""
}

View file

@ -36,16 +36,19 @@ help_data = {
-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, --force-wallet-dat Force the use of wallet.dat as a key source
-i, --info Display information about the transaction and exit
-I, --tx_id Display transaction ID and exit
-k, --keys-from-file k Provide additional key data from file 'k'
-q, --quiet Suppress warnings; overwrite files without asking
-P, --passwd-file f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-b, --from-brain l,p Generate keys from a user-created password,
i.e. a "brainwallet", using seed length 'l' and
hash preset 'p' (comma-separated)
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
-s, --from-seed Generate keys from a seed in .{} format
-w, --use-wallet-dat Use the keys in the bitcoind wallet.dat file too
Transactions with either mmgen or non-mmgen input addresses may be signed.
For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
@ -70,9 +73,10 @@ Seed data supplied in files must have the following extensions:
""".format(seed_ext,wallet_ext,seed_ext,mn_ext,brain_ext)
}
short_opts = "hd:efik:qb:ms"
long_opts = "help","outdir=","echo_passphrase","force_wallet_dat","info",\
"keys_from_file=","quiet","from_brain=","from_mnemonic","from_seed"
short_opts = "hd:eiIk:P:qb:msw"
long_opts = "help","outdir=","echo_passphrase","info","tx_id",\
"keys_from_file=","passwd_file=","quiet","from_brain=",\
"from_mnemonic","from_seed","use_wallet_dat"
opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
@ -83,62 +87,18 @@ if 'keys_from_file' in opts: check_infile(opts['keys_from_file'])
if not infiles: usage(help_data)
for i in infiles: check_infile(i)
# Begin execution
c = connect_to_bitcoind()
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles):
tx_file = infiles.pop(0)
tx_data = get_lines_from_file(tx_file,"transaction data")
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,metadata)
sys.exit(0)
if not 'quiet' in opts: do_license_msg()
msg("Successfully opened transaction file '%s'" % tx_file)
if user_confirm("View transaction data? ",default_yes=False):
view_tx_data(c,inputs_data,tx_hex,metadata)
# Are inputs mmgen addresses?
mmgen_addrs,other_addrs,keys = [],[],[]
for i in inputs_data:
if verify_mmgen_label(i['account']):
mmgen_addrs.append(i)
else:
other_addrs.append(i)
if mmgen_addrs and not 'force_wallet_dat' in opts:
# Check that all the seed IDs are the same:
seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
ext_data = (
(wallet_ext, {}),
(mn_ext, {"from_mnemonic":True}),
(seed_ext, {"from_seed": True}),
(brain_ext, opts)
)
seed_ids_save = seed_ids[0:]
keys = []
while seed_ids:
infile = False
if infiles:
infile = infiles.pop()
ext = infile.split(".")[-1]
for e,o in ext_data:
if e == ext:
if e == brain_ext:
if "from_brain" not in opts:
msg(
"'--from-brain' option must be specified for brainwallet file")
sys.exit(2)
seed = get_seed_retry(infile,o); break
else:
msg("Invalid file extension: '.%s'\nValid extensions: '.%s'" %
(ext,"' '.".join([i[0] for i in ext_data])))
sys.exit(2)
seed = get_seed(infile,opts)
elif "from_brain" in opts or "from_mnemonic" in opts or "from_seed" in opts:
msg("Need data for seed ID %s" % seed_ids[0])
seed = get_seed_retry("",opts)
@ -158,65 +118,123 @@ if mmgen_addrs and not 'force_wallet_dat' in opts:
from mmgen.addr import generate_keys
keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if infile:
msg("Invalid input file: %s" % infile)
sys.exit(2)
if seed_id in seed_ids_save:
msg_r("Ignoring duplicate seed source")
if infile: msg(" '%s'" % infile)
else: msg(" for ID %s" % seed_id)
else:
msg("Seed source produced an invalid seed ID (%s)" % seed_id)
if infile:
msg("Invalid input file: %s" % infile)
sys.exit(2)
if other_addrs:
if 'keys_from_file' in opts:
keys += get_lines_from_file(opts['keys_from_file'],
"additional key data")
else:
msg("""
A key file must be supplied (option '-f') for the following non-mmgen
address%s: %s""" % (
"" if len(other_addrs) == 1 else "es",
" ".join([i['address'] for i in other_addrs])
))
sys.exit(2)
return keys
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif 'keys_from_file' in opts:
keys = get_lines_from_file(opts['keys_from_file'],"key data")
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
prompt = "Enter passphrase for bitcoind wallet: "
if 'echo_passphrase' in opts:
password = my_raw_input(prompt)
else:
password = my_getpass(prompt)
wallet_enc = True
from mmgen.rpc import exceptions
def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys):
try:
c.walletpassphrase(password, 9999)
except exceptions.WalletWrongEncState:
msg("Wallet is unencrypted")
wallet_enc = False
except exceptions.WalletPassphraseIncorrect:
msg("Passphrase incorrect")
sys.exit(3)
except exceptions.WalletAlreadyUnlocked:
msg("WARNING: Wallet already unlocked!")
else:
msg("Passphrase OK")
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
except:
from mmgen.rpc import exceptions
msg("Using keys in wallet.dat as per user request")
prompt = "Enter passphrase for bitcoind wallet: "
while True:
passwd = get_bitcoind_passphrase(prompt,opts)
sig_tx = sign_transaction(c,tx_hex,sig_data)
try:
c.walletpassphrase(passwd, 9999)
except exceptions.WalletPassphraseIncorrect:
msg("Passphrase incorrect")
else:
msg("Passphrase OK"); break
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
if wallet_enc:
c.walletlock()
msg("Locking wallet")
try:
c.walletlock()
except:
msg("Failed to lock wallet")
return sig_tx
def missing_keys_errormsg(other_addrs):
msg("""
A key file (option '-f') or wallet.dat (option '-w') must be supplied
for the following non-mmgen address%s: %s""" %
("" if len(other_addrs) == 1 else "es",
" ".join([i['address'] for i in other_addrs])
))
# Begin execution
c = connect_to_bitcoind()
tx_file = infiles.pop(0)
m = "" if 'tx_id' in opts else "transaction data"
tx_data = get_lines_from_file(tx_file,m)
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
if 'tx_id' in opts:
msg(metadata[0])
sys.exit(0)
if 'info' in opts:
view_tx_data(c,inputs_data,tx_hex,metadata)
sys.exit(0)
if not 'quiet' in opts: do_license_msg()
msg("Successfully opened transaction file '%s'" % tx_file)
if user_confirm("View transaction data? ",default_yes=False):
view_tx_data(c,inputs_data,tx_hex,metadata)
# Are inputs mmgen addresses?
mmgen_addrs,other_addrs = [],[]
for i in inputs_data:
if verify_mmgen_label(i['account']):
mmgen_addrs.append(i)
else:
other_addrs.append(i)
if 'keys_from_file' in opts:
keys = get_lines_from_file(opts['keys_from_file'],"key data")
else:
keys = []
if mmgen_addrs:
if other_addrs and not keys and not 'use_wallet_dat' in opts:
missing_keys_errormsg(other_addrs)
sys.exit(2)
keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles)
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
else:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
elif other_addrs:
if 'use_wallet_dat' in opts:
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys)
else:
if keys:
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
else:
missing_keys_errormsg(other_addrs)
sys.exit(2)
if sig_tx['complete']:
msg("Signing completed")
prompt = "Save signed transaction?"
if user_confirm(prompt,default_yes=True):
print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
else:
msg("Some keys were missing. Transaction could not be signed.")
sys.exit(3)
prompt = "Save signed transaction?"
if user_confirm(prompt,default_yes=True):
print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)

View file

@ -36,15 +36,16 @@ help_data = {
-d, --outdir d Specify an alternate directory 'd' for output
-e, --echo-passphrase Print passphrase to screen when typing it
-m, --export-mnemonic Export the wallet's mnemonic to file
-P, --passwd-file f Get passphrase from file 'f'
-s, --export-seed Export the wallet's seed to file
-S, --stdout Print seed or mnemonic data to standard output
-v, --verbose Produce more verbose output
"""
}
short_opts = "hd:emsSv"
short_opts = "hd:emP:sSv"
long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\
"export_seed","stdout","verbose"
"passwd_file=","export_seed","stdout","verbose"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)

View file

@ -37,14 +37,16 @@ help_data = {
-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: {}
(default: {})
-L, --label l Label to identify this wallet (32 chars max.
Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
-p, --hash-preset p Use scrypt.hash() parameters from preset 'p'
(default: '{}')
-P, --show-hash-presets Show information on available hash presets
-q, --quiet Suppress warnings; overwrite files without asking
-P, --passwd-file f Get passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without
prompting
-u, --usr-randlen n Get 'n' characters of randomness from the user
(default: {})
@ -77,11 +79,11 @@ 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.
For a brainwallet passphrase to always generate the same keys and
addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
in all future invocations with that passphrase.
""".format(
", ".join([str(i) for i in seed_lens]),
",".join([str(i) for i in seed_lens]),
seed_len,
hash_preset,
usr_randlen,
@ -90,10 +92,10 @@ invocations with that passphrase.
)
}
short_opts = "hd:el:L:p:Pqu:b:ms"
long_opts = "help","outdir=","echo_passphrase","seed_len=","label=",\
"hash_preset=","show_hash_presets","quiet","usr_randlen=",\
"from_brain=","from_mnemonic","from_seed"
short_opts = "hd:eHl:L:p:P:qu:b:ms"
long_opts = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
"from_brain=","from_mnemonic","from_seed"
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
@ -131,6 +133,9 @@ if not 'quiet' in opts: do_license_msg()
msg_r("Acquiring random data from your computer...")
from time import sleep
sleep(1)
try:
from Crypto import Random
r = Random.new()
@ -162,8 +167,15 @@ else:
salt = os_rand_data[1] + usr_rand_data
salt = sha256(salt).digest()[:salt_len]
passwd = get_first_passphrase_from_user(
"{} wallet passphrase".format(proj_name), opts)
if not 'quiet' in opts:
msg("""
Now you must choose a passphrase to encrypt the seed with. A key will be
generated from your passphrase using a hash preset of '%s'. Please note that
no strength checking of passphrases is performed. For an empty passphrase,
just hit ENTER twice.
""" % opts['hash_preset'])
passwd = get_new_passphrase("{} wallet passphrase".format(proj_name), opts)
key = make_key(passwd, salt, opts['hash_preset'])

View file

@ -38,9 +38,9 @@ def test_for_keyconv():
p = Popen([keyconv_exec, '-h'], stdout=PIPE, stderr=PIPE)
except:
sys.stderr.write("""
Executable '%s' unavailable. Falling back on (slow) internal ECDSA library.
Please install '%s' from the %s package on your system for much faster
address generation.
Executable '%s' unavailable. Falling back on (slow) internal ECDSA library.
Please install '%s' from the %s package on your system for much
faster address generation.
""" % (keyconv_exec, keyconv_exec, "vanitygen"))
return False
@ -152,12 +152,14 @@ def format_addr_data(addrlist, seed_chksum, opts):
# MMGen address file
#
# This file is editable.
# Everything following a hash symbol '#' is ignored.
# A label may be added to the right of each address, and it will be
# appended to the bitcoind wallet label upon import (max. {} characters,
# allowed characters: A-Za-z0-9, plus '{}').
""".format(max_wallet_addr_label_len,
"', '".join(wallet_addr_label_symbols)).strip()
# Everything following a hash symbol '#' is a comment and ignored by {}.
# A text label of {} characters or less may be added to the right of each
# address, and it will be appended to the bitcoind wallet label upon import.
# The label may contain ASCII letters, numerals, and the symbols
# '{}' and '{}'.
""".format(proj_name.capitalize(),max_wallet_addr_label_len,
"', '".join(wallet_addr_label_symbols[0:-1]),
wallet_addr_label_symbols[-1]).strip()
data = []
if not 'stdout' in opts: data.append(header + "\n")
data.append("%s {" % seed_chksum.upper())

View file

@ -19,10 +19,14 @@
config.py: Constants and configuration options for the mmgen suite
"""
proj_name = "mmgen"
wallet_ext = "mmdat"
seed_ext = "mmseed"
mn_ext = "mmwords"
brain_ext = "mmbrain"
seed_exts = wallet_ext, seed_ext, mn_ext, brain_ext
default_wl = "electrum"
#default_wl = "tirosh"

View file

@ -30,7 +30,7 @@ gpl = {
and you are welcome to redistribute it under certain conditions.
""",
'prompt': """
Press 'c' for conditions, 'w' for warranty info, or ENTER to continue:
Press 'w' for conditions and warranty info, or 'c' to continue:
""",
'conditions': """
TERMS AND CONDITIONS
@ -582,60 +582,20 @@ reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
""",
'warranty': """
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
"""
}
def do_pager(text):
import os
pager = os.environ['PAGER'] if 'PAGER' in os.environ else 'more'
p = os.popen(pager, 'w')
p.write(text)
p.close()
msg_r("\r")
def do_license_msg():
msg(gpl['warning'])
prompt = "%s " % gpl['prompt'].strip()
while True:
prompt = "%s " % gpl['prompt'].strip()
reply = get_char(prompt)
if reply == 'c': do_pager(gpl['conditions'])
elif reply == 'w': do_pager(gpl['warranty'])
else: msg(""); break
if reply == 'w':
from mmgen.utils import do_pager
do_pager(gpl['conditions'],"END OF CONDITIONS AND WARRANTY")
elif reply == 'c':
msg(""); break
else:
msg_r("\r")
msg("")

View file

@ -49,12 +49,12 @@ def get_seed_from_mnemonic(mn,wl):
if len(mn) not in mnemonic_lens:
msg("Bad mnemonic (%i words). Allowed numbers of words: %s" %
(len(mn)," ".join([str(i) for i in mnemonic_lens])))
(len(mn),", ".join([str(i) for i in mnemonic_lens])))
return False
for w in mn:
for n,w in enumerate(mn,1):
if w not in wl:
msg("Bad mnemonic: '%s' is not in the wordlist" % w)
msg("Bad mnemonic: word number %s is not in the wordlist" % n)
return False
from binascii import unhexlify

View file

@ -64,7 +64,12 @@ class BitcoinConnection(object):
try:
return self.proxy.importaddress(address,label)
except JSONRPCException as e:
raise _wrap_exception(e.error)
if e.error['message'] == "Method not found":
from mmgen.utils import msg
msg("""
ERROR: 'importaddress' method not found. Is your bitcoind enabled for
watch-only addresses?""")
else: raise _wrap_exception(e.error)
# sendrawtransaction <hex string> [allowhighfees=false]
def sendrawtransaction(self,tx):

View file

@ -104,8 +104,9 @@ class AuthServiceProxy(object):
'Authorization' : self.__authhdr,
'Content-type' : 'application/json' })
except:
print "Unable to connect to bitcoind. Exiting"
from mmgen.utils import msg
import sys
msg("\nUnable to connect to bitcoind.")
sys.exit(2)
httpresp = self.__conn.getresponse()

View file

@ -65,9 +65,10 @@ def connect_to_bitcoind():
return c
def trim_exponent(d):
def trim_exponent(n):
'''Remove exponent and trailing zeros.
'''
d = Decimal(n)
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
@ -95,7 +96,16 @@ def check_btc_amt(send_amt):
def get_cfg_options(cfg_keys):
cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
if "HOME" in os.environ:
cfg_file = "%s/%s" % (os.environ["HOME"], ".bitcoin/bitcoin.conf")
elif "HOMEPATH" in os.environ:
# Windows:
cfg_file = "%s%s" % (os.environ["HOMEPATH"],
r"\Application Data\Bitcoin\bitcoin.conf")
else:
msg("Unable to find bitcoin configuration file")
sys.exit(3)
try:
f = open(cfg_file)
except:
@ -186,7 +196,7 @@ def sort_and_view(unspent):
total
)]
output.append(fs % ("Num","TX id Vout","","Address","Amount (BTC)",
"Age (days)"))
"Age(days)"))
for i in out:
amt = str(trim_exponent(i.amount))
@ -209,12 +219,17 @@ def sort_and_view(unspent):
output.append(fs % (str(n+1)+")",txid,i.vout,addr,i.amt,i.days))
skip_body = False
while True:
reply = get_char("\n".join(output) +
"""\n
if skip_body: skip_body = False
else:
msg("\n".join(output))
msg("""
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
View options: [g]roup, show [m]mgen addr
(Type 'q' to quit sorting, 'p' to print to file): """).strip()
View options: [g]roup, show [m]mgen addr""")
reply = get_char(
"(Type 'q' to quit sorting, 'p' to print to file, 'P' to view in pager): ")
if reply == 'a': unspent.sort(s_amt); sort = "amount"; break
elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
@ -241,13 +256,19 @@ View options: [g]roup, show [m]mgen addr
i.address,mmid,i.amt,i.days,cmt)
pout.append(os.rstrip())
outdata = "Unspent outputs ({} UTC)\n\n{}\n\nTotal BTC: {}\n".format(
make_timestr(), "\n".join(pout), total
)
outfile = "listunspent.out"
sort_info = (
("reverse," if reverse else "") +
(sort if sort else "unsorted")
)
outdata = \
"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
make_timestr(), sort_info, "\n".join(pout), total
)
outfile = "listunspent[%s].out" % sort_info
write_to_file(outfile, outdata)
skip_body = True
msg("\nData written to '%s'" % outfile)
sys.exit(1)
elif reply == 'P': do_pager("\n".join(output))
elif reply == 'q': break
else: msg("Invalid input")
@ -288,7 +309,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
msg("TRANSACTION DATA:\n")
if metadata: msg(
"Header: [ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
"Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n".format(*metadata))
msg("Inputs:")
total_in = 0
@ -300,7 +321,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
msg(" " + """
%-2s tx,vout: %s,%s
address: %s
label: %s
ID/label: %s
amount: %s BTC
confirmations: %s (around %s days)
""".strip() %

View file

@ -40,20 +40,44 @@ def my_getpass(prompt):
return pw
def get_char(prompt):
term = False
def get_char(prompt=""):
import os
msg_r(prompt)
os.system(
"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
)
try: ch = sys.stdin.read(1)
global term
if not term:
try:
import tty, termios
term = "unix"
except:
try:
import msvcrt
term = "mswin"
except:
msg("Unable to set terminal mode")
sys.exit(2)
try:
if term == "unix":
import tty, termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
tty.setcbreak(fd)
ch = sys.stdin.read(1)
elif term == "mswin":
import msvcrt
ch = msvcrt.getch()
if ord(ch) == 3:
raise KeyboardInterrupt
except:
os.system("stty sane")
msg("\nUser interrupt")
sys.exit(1)
else:
os.system("stty sane")
finally:
if term == "unix":
termios.tcsetattr(fd, termios.TCSADRAIN, old)
return ch
@ -320,39 +344,29 @@ def parse_address_list(arg,sep=","):
return sorted(set(ret))
def get_first_passphrase_from_user(what, opts):
"""
Prompt the user for a passphrase and return it
def get_new_passphrase(what, opts):
Supported options: echo_passphrase
"""
if not 'quiet' in opts:
msg("""
Now you must choose a passphrase to encrypt the seed with. A key will be
generated from your passphrase using a hash preset of '%s'. Please note that
no strength checking of passphrases is performed. For an empty passphrase,
just hit ENTER twice.
""" % opts['hash_preset'])
if 'echo_passphrase' in opts:
ret = " ".join(_get_words_from_user(opts,"Enter %s: " % what))
if ret == "": msg("Empty passphrase")
return ret
for i in range(passwd_max_tries):
ret = " ".join(_get_words_from_user(opts,"Enter %s: " % what))
ret2 = " ".join(_get_words_from_user(opts,"Repeat %s: " % what))
if debug: print "Passphrases: [%s] [%s]" % (ret,ret2)
if ret2 == ret:
s = " (empty)" if not len(ret) else ""
msg("%ss match%s" % (what.capitalize(),s))
return ret
if 'passwd_file' in opts:
pw = " ".join(_get_words_from_file(opts['passwd_file'],what))
elif 'echo_passphrase' in opts:
pw = " ".join(_get_words_from_user(("Enter %s: " % what), opts))
else:
for i in range(passwd_max_tries):
pw = " ".join(_get_words_from_user(("Enter %s: " % what),opts))
pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
if debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
if pw == pw2:
msg("%ss match" % what.capitalize())
break
else:
msg("%ss do not match" % what.capitalize())
else:
msg("%ss do not match" % what.capitalize())
msg("User failed to duplicate passphrase in %s attempts" %
passwd_max_tries)
sys.exit(2)
msg("User failed to duplicate passphrase in %s attempts" % passwd_max_tries)
sys.exit(2)
if pw == "": msg("WARNING: Empty passphrase")
return pw
def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
@ -404,9 +418,12 @@ def write_to_stdout(data, what, confirm=True):
if sys.stdout.isatty() and confirm:
confirm_or_exit("",'output {} to screen'.format(what))
elif not sys.stdout.isatty():
import os
of = os.readlink("/proc/%d/fd/1" % os.getpid())
msg("Writing data to file '%s'" % of)
try:
import os
of = os.readlink("/proc/%d/fd/1" % os.getpid())
msg("Redirecting output to file '%s'" % of)
except:
msg("Redirecting output to file")
sys.stdout.write(data)
@ -682,24 +699,28 @@ def get_data_from_wallet(infile,opts,silent=False):
return label,metadata,hash_preset,res['salt'],res['enc_seed']
def _get_words_from_user(opts, prompt):
def _get_words_from_user(prompt, opts):
# split() also strips
if 'echo_passphrase' in opts:
return my_raw_input(prompt).split()
words = my_raw_input(prompt).split()
else:
return my_getpass(prompt).split()
words = my_getpass(prompt).split()
if debug: print "Sanitized input: [%s]" % " ".join(words)
return words
def _get_words_from_file(infile,what):
msg("Getting %s from file '%s'" % (what,infile))
f = open_file_or_exit(infile, 'r')
data = f.read(); f.close()
# split() also strips
return data.split()
words = f.read().split()
f.close()
if debug: print "Sanitized input: [%s]" % " ".join(words)
return words
def get_lines_from_file(infile,what):
msg("Getting %s from file '%s'" % (what,infile))
def get_lines_from_file(infile,what=""):
if what != "": msg("Getting %s from file '%s'" % (what,infile))
f = open_file_or_exit(infile,'r')
lines = f.read().splitlines(); f.close()
return lines
@ -713,15 +734,6 @@ def get_data_from_file(infile,what="data"):
return data
def get_words(infile,what,prompt,opts):
if infile:
words = _get_words_from_file(infile,what)
else:
words = _get_words_from_user(opts,prompt)
if debug: print "Sanitized input: [%s]" % " ".join(words)
return words
def _get_seed_from_seed_data(words):
if not _check_mmseed_format(words):
@ -746,6 +758,34 @@ def _get_seed_from_seed_data(words):
msg("Invalid checksum for {} seed".format(proj_name))
return False
passwd_file_used = False
def mark_passwd_file_as_used(opts):
global passwd_file_used
if passwd_file_used:
msg_r("WARNING: Reusing passphrase from file '%s'." % opts['passwd_file'])
msg(" This may not be what you want!")
passwd_file_used = True
def get_mmgen_passphrase(prompt,opts):
if 'passwd_file' in opts:
mark_passwd_file_as_used(opts)
return " ".join(_get_words_from_file(opts['passwd_file'],"passphrase"))
else:
return " ".join(_get_words_from_user(prompt,opts))
def get_bitcoind_passphrase(prompt,opts):
if 'passwd_file' in opts:
mark_passwd_file_as_used(opts)
return get_data_from_file(opts['passwd_file'],"passphrase").strip("\r\n")
else:
if 'echo_passphrase' in opts:
return my_raw_input(prompt)
else:
return my_getpass(prompt)
def get_seed_from_wallet(
infile,
@ -759,7 +799,7 @@ def get_seed_from_wallet(
if 'verbose' in opts: _display_control_data(*wdata)
passwd = " ".join(get_words("","",prompt,opts))
passwd = get_mmgen_passphrase(prompt,opts)
key = make_key(passwd, salt, hash_preset)
@ -809,41 +849,63 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
return dec_seed
def get_seed(infile,opts,silent=False):
if 'from_mnemonic' in opts:
prompt = "Enter mnemonic: "
what = "mnemonic"
words = get_words(infile,"mnemonic data",prompt,opts)
def _get_words(infile,what,prompt,opts):
if infile:
return _get_words_from_file(infile,what)
else:
return _get_words_from_user(prompt,opts)
def get_seed(infile,opts,silent=False):
ext = infile.split(".")[-1]
if ext == mn_ext: source = "mnemonic"
elif ext == brain_ext: source = "brainwallet"
elif ext == seed_ext: source = "seed"
elif ext == wallet_ext: source = "wallet"
elif 'from_mnemonic' in opts: source = "mnemonic"
elif 'from_brain' in opts: source = "brainwallet"
elif 'from_seed' in opts: source = "seed"
else:
if infile: msg(
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
(infile, "', '.".join(seed_exts)))
else: msg("No seed source type specified and no file supplied")
sys.exit(2)
if source == "mnemonic":
prompt = "Enter mnemonic: "
words = _get_words(infile,"mnemonic data",prompt,opts)
wl = get_default_wordlist()
from mmgen.mnemonic import get_seed_from_mnemonic
seed = get_seed_from_mnemonic(words,wl)
elif 'from_brain' in opts:
elif source == "brainwallet":
if 'from_brain' not in opts:
msg("'--from-brain' parameters must be specified for brainwallet file")
sys.exit(2)
if 'quiet' not in opts:
confirm_or_exit(
cmessages['brain_warning'].format(
proj_name.capitalize(),
*_get_from_brain_opt_params(opts)),
"continue")
proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
"continue")
prompt = "Enter brainwallet passphrase: "
what = "brainwallet"
words = get_words(infile,"brainwallet data",prompt,opts)
words = _get_words(infile,"brainwallet data",prompt,opts)
seed = _get_seed_from_brain_passphrase(words,opts)
elif 'from_seed' in opts:
elif source == "seed":
prompt = "Enter seed in %s format: " % seed_ext
what = "seed"
words = get_words(infile,"seed data",prompt,opts)
words = _get_words(infile,"seed data",prompt,opts)
seed = _get_seed_from_seed_data(words)
else:
return get_seed_from_wallet(infile, opts, silent=silent)
elif source == "wallet":
seed = get_seed_from_wallet(infile, opts, silent=silent)
if infile and not seed:
msg("Invalid %s file: %s" % (what,infile))
msg("Invalid %s file: %s" % (source,infile))
sys.exit(2)
return seed
# Repeat if data entry is incorrect
# Repeat if entered data is invalid
def get_seed_retry(infile,opts):
silent = False
while True:
@ -863,5 +925,48 @@ def remove_blanks_comments(lines):
return ret
def do_pager(text,endmsg=""):
import os
if sys.platform.startswith("linux"):
if 'PAGER' in os.environ and os.environ['PAGER']:
try:
p = os.popen(os.environ['PAGER'], 'w')
except:
print text
else:
try:
p.write(text)
p.close()
except:
p.close()
msg_r("\r")
else:
print text
elif sys.platform.startswith("win"):
try:
import msvcrt
except:
print text
else:
try:
from subprocess import Popen, PIPE, STDOUT
p = Popen(["more","/C"], stdin=PIPE, shell=True)
if endmsg:
p.stdin.write("%s\n%s\n\n" % (text,endmsg))
else:
p.stdin.write(text)
except:
msg("\nUser exit")
from time import sleep
# Flush stdin
while msvcrt.kbhit(): msvcrt.getch()
sleep(1)
while msvcrt.kbhit(): msvcrt.getch()
msg("")
else:
print text
if __name__ == "__main__":
print get_lines_from_file("/tmp/lines","test file")
print "utils.py"

View file

@ -20,7 +20,7 @@ walletgen.py: Routines used for seed generation and wallet creation
"""
import sys
from mmgen.utils import msg, msg_r
from mmgen.utils import msg, msg_r, get_char
from binascii import hexlify
def get_random_data_from_user(opts):
@ -47,30 +47,18 @@ displayed on the screen.
user_rand_data,intervals = "",[]
try:
import os
os.system(
"stty -icanon min 1 time 0 -echo -echoe -echok -echonl -crterase noflsh"
)
for i in range(ulen):
user_rand_data += sys.stdin.read(1)
msg_r("\r" + prompt % (ulen - i - 1))
now = time.time()
intervals.append(now - saved_time)
saved_time = now
if 'quiet' in opts:
msg_r("\r")
else:
msg_r("\rThank you. That's enough." + " "*15 + "\n\n")
time.sleep(0.5)
msg_r(
"User random data successfully acquired. Press ENTER to continue: ")
raw_input()
except:
msg("\nUser random input interrupted")
sys.exit(1)
finally:
os.system("stty sane")
for i in range(ulen):
user_rand_data += get_char()
msg_r("\r" + prompt % (ulen - i - 1))
now = time.time()
intervals.append(now - saved_time)
saved_time = now
if 'quiet' in opts:
msg_r("\r")
else:
msg_r("\rThank you. That's enough." + " "*15 + "\n\n")
time.sleep(0.5)
get_char("User random data successfully acquired. Press ENTER to continue: ")
return user_rand_data, ["{:.22f}".format(i) for i in intervals]

View file

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