From dcab2602b6703834d6f990fd093eeceaf34f9159 Mon Sep 17 00:00:00 2001 From: philemon Date: Sat, 22 Mar 2014 20:39:54 +0400 Subject: [PATCH] Windows port fully functional; fixes, enhancements --- mmgen-addrgen | 36 +++--- mmgen-addrimport | 6 +- mmgen-passchg | 17 +-- mmgen-pywallet | 42 +++---- mmgen-txcreate | 25 ++-- mmgen-txsend | 2 +- mmgen-txsign | 222 ++++++++++++++++++---------------- mmgen-walletchk | 5 +- mmgen-walletgen | 36 ++++-- mmgen/addr.py | 20 +-- mmgen/config.py | 4 + mmgen/license.py | 60 ++------- mmgen/mnemonic.py | 6 +- mmgen/rpc/connection.py | 7 +- mmgen/rpc/proxy.py | 3 +- mmgen/tx.py | 49 +++++--- mmgen/utils.py | 261 ++++++++++++++++++++++++++++------------ mmgen/walletgen.py | 38 ++---- setup.py | 2 +- 19 files changed, 480 insertions(+), 361 deletions(-) diff --git a/mmgen-addrgen b/mmgen-addrgen index e88eb62a..3cb6c725 100755 --- a/mmgen-addrgen +++ b/mmgen-addrgen @@ -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]
", '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 diff --git a/mmgen-addrimport b/mmgen-addrimport index 05364dec..02251abe 100755 --- a/mmgen-addrimport +++ b/mmgen-addrimport @@ -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 """ } diff --git a/mmgen-passchg b/mmgen-passchg index 9bc37adb..57391fc0 100755 --- a/mmgen-passchg +++ b/mmgen-passchg @@ -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") diff --git a/mmgen-pywallet b/mmgen-pywallet index 27890e4b..1ac4b8ab 100755 --- a/mmgen-pywallet +++ b/mmgen-pywallet @@ -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" \ diff --git a/mmgen-txcreate b/mmgen-txcreate index 01af238e..60c445e3 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -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] diff --git a/mmgen-txsend b/mmgen-txsend index 3407f944..f3ef0ed7 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -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 """ } diff --git a/mmgen-txsign b/mmgen-txsign index eda1bfe6..3ce132b3 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -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) diff --git a/mmgen-walletchk b/mmgen-walletchk index 335531fb..6e3c16cb 100755 --- a/mmgen-walletchk +++ b/mmgen-walletchk @@ -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) diff --git a/mmgen-walletgen b/mmgen-walletgen index 8e9ceb17..9bad153f 100755 --- a/mmgen-walletgen +++ b/mmgen-walletgen @@ -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']) diff --git a/mmgen/addr.py b/mmgen/addr.py index fd7518a8..295d0cc2 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -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()) diff --git a/mmgen/config.py b/mmgen/config.py index cea4161d..d0925d6d 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -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" diff --git a/mmgen/license.py b/mmgen/license.py index 72eb76a2..e29df791 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -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("") diff --git a/mmgen/mnemonic.py b/mmgen/mnemonic.py index 8144b1ff..34a5c8e5 100755 --- a/mmgen/mnemonic.py +++ b/mmgen/mnemonic.py @@ -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 diff --git a/mmgen/rpc/connection.py b/mmgen/rpc/connection.py index 19482895..6623ddda 100755 --- a/mmgen/rpc/connection.py +++ b/mmgen/rpc/connection.py @@ -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 [allowhighfees=false] def sendrawtransaction(self,tx): diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py index 1457747b..a22decfe 100755 --- a/mmgen/rpc/proxy.py +++ b/mmgen/rpc/proxy.py @@ -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() diff --git a/mmgen/tx.py b/mmgen/tx.py index ac160110..62970d82 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -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() % diff --git a/mmgen/utils.py b/mmgen/utils.py index 7ac72124..09aa4f76 100755 --- a/mmgen/utils.py +++ b/mmgen/utils.py @@ -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" diff --git a/mmgen/walletgen.py b/mmgen/walletgen.py index 1640f895..3acd5c90 100755 --- a/mmgen/walletgen.py +++ b/mmgen/walletgen.py @@ -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] diff --git a/setup.py b/setup.py index 7b8ee368..48c26fe6 100755 --- a/setup.py +++ b/setup.py @@ -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',