From 08c5b76805c7af93c17a6b8e37fffadbcdbfc2a6 Mon Sep 17 00:00:00 2001 From: philemon Date: Wed, 9 Apr 2014 22:45:23 +0400 Subject: [PATCH] Version 0.7.0 --- MANIFEST | 24 +- README.md | 4 +- doc/MMGenBuildBitcoindMSWin.md | 2 +- doc/MMGenGettingStarted.md | 8 +- doc/MMGenInstallLinux.md | 2 +- doc/MMGenTextEditors.md | 2 +- mmgen-addrgen | 8 +- mmgen-addrimport | 23 +- mmgen-passchg | 25 +- mmgen-pywallet | 26 +- mmgen-txcreate | 136 ++++---- mmgen-txsend | 7 +- mmgen-txsign | 87 +++--- mmgen-walletchk | 13 +- mmgen-walletgen | 35 ++- mmgen/Opts.py | 6 +- mmgen/addr.py | 36 +-- mmgen/bitcoin.py | 53 ++-- mmgen/config.py | 17 +- mmgen/license.py | 4 + mmgen/rpc/proxy.py | 2 +- mmgen/term.py | 191 ++++++++++++ mmgen/tests/bitcoin.py | 68 ++-- mmgen/tests/test.py | 2 +- mmgen/tx.py | 550 +++++++++++++++++++++------------ mmgen/util.py | 226 ++++---------- mmgen/walletgen.py | 7 +- setup.py | 3 +- 28 files changed, 937 insertions(+), 630 deletions(-) create mode 100755 mmgen/term.py diff --git a/MANIFEST b/MANIFEST index f5bba605..61613e28 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,7 +1,7 @@ # file GENERATED by distutils, do NOT edit +__init__.py mmgen-addrgen mmgen-addrimport -mmgen-keygen mmgen-passchg mmgen-pywallet mmgen-txcreate @@ -19,8 +19,9 @@ mmgen/license.py mmgen/mn_electrum.py mmgen/mn_tirosh.py mmgen/mnemonic.py +mmgen/term.py mmgen/tx.py -mmgen/utils.py +mmgen/util.py mmgen/walletgen.py mmgen/rpc/__init__.py mmgen/rpc/config.py @@ -29,13 +30,12 @@ mmgen/rpc/data.py mmgen/rpc/exceptions.py mmgen/rpc/proxy.py mmgen/rpc/util.py -scripts/bitcoind-walletunlock.py -scripts/deinstall.sh -tests/addr.py -tests/bitcoin.py -tests/mn_electrum.py -tests/mn_tirosh.py -tests/mnemonic.py -tests/test.py -tests/utils.py -tests/walletgen.py +mmgen/tests/__init__.py +mmgen/tests/addr.py +mmgen/tests/bitcoin.py +mmgen/tests/mn_electrum.py +mmgen/tests/mn_tirosh.py +mmgen/tests/mnemonic.py +mmgen/tests/test.py +mmgen/tests/util.py +mmgen/tests/walletgen.py diff --git a/README.md b/README.md index 8e76b030..c1a5ed08 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ addresses can be tracked and spent as well, creating an easy migration path from other wallets. To track address balances, MMGen relies on a bitcoin daemon modified by -Bitcoin's core developer team to support watch-only addresses. This feature -is soon to be included in the mainline Satoshi build. In the meantime, +Bitcoin core developers to support watch-only addresses. This feature +will one day be included in the mainline Satoshi build. In the meantime, instructions are provided below for compiling the modified bitcoind. Under Linux, this is a trivial task for even a casual user. Unfortunately, the same can't be said for Windows, but the thoroughly tested, step-by-step Windows build diff --git a/doc/MMGenBuildBitcoindMSWin.md b/doc/MMGenBuildBitcoindMSWin.md index 5c2a1946..c22c4617 100644 --- a/doc/MMGenBuildBitcoindMSWin.md +++ b/doc/MMGenBuildBitcoindMSWin.md @@ -66,7 +66,7 @@ time-consuming process of compiling the whole boost package. #### 5. Build Bitcoind: -Download Sipa's watchonly bitcoind [zip archive][05] (commit a13f1e8 [[check][]]) +Download Sipa's watchonly bitcoind [zip archive][05] (commit #a13f1e8 [[check][]]) from GitHub and unpack it. At the MSYS prompt, run: $ cd /c/bitcoin-watchonly diff --git a/doc/MMGenGettingStarted.md b/doc/MMGenGettingStarted.md index 72f8a803..7b39614e 100644 --- a/doc/MMGenGettingStarted.md +++ b/doc/MMGenGettingStarted.md @@ -154,7 +154,7 @@ sending it to the addresses labeled "Storage 1", "Storage 2" and "Storage 3" To refresh your memory, here are the three addresses in question: - $ cat my_addrs + $ cat my.addrs # My first MMGen addresses 89ABCDEF { 1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations @@ -175,11 +175,7 @@ the change amount (3.399 BTC in this case) automatically. Alternatively, and more conveniently, you can list your three addresses in MMGen format: - $ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4 my_addrs - -Note that an MMGen address file containing the requested output addresses must -be provided on the command line. Any extra addresses in the file will be -ignored. + $ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4 Now hit ENTER, choose the transaction's input from the list (10 BTC, address 1F9495H8EJL..., txid 04f97185...,2), and confirm. If all goes well, diff --git a/doc/MMGenInstallLinux.md b/doc/MMGenInstallLinux.md index 0f8ba147..481d0024 100644 --- a/doc/MMGenInstallLinux.md +++ b/doc/MMGenInstallLinux.md @@ -58,7 +58,7 @@ the version should be 1.48 or greater): libboost-test-dev libboost-thread-dev -Download the bitcoin-watchonly [zip archive][00] (commit a13f1e8 [[check][]]) +Download the bitcoin-watchonly [zip archive][00] (commit #a13f1e8 [[check][]]) from GitHub, configure, and build: $ unzip watchonly.zip diff --git a/doc/MMGenTextEditors.md b/doc/MMGenTextEditors.md index 6b2a58ec..eed8c249 100644 --- a/doc/MMGenTextEditors.md +++ b/doc/MMGenTextEditors.md @@ -2,7 +2,7 @@ MMGen = Multi-Mode GENerator ============================ ##### a Bitcoin cold storage solution for the command line -#### A word on text editors: +### A word on text editors: The text file editors that come with Windows, "edit" and "notepad", are unsuitable for editing source code for many reasons, but above all because they diff --git a/mmgen-addrgen b/mmgen-addrgen index d27295f0..e59fc625 100755 --- a/mmgen-addrgen +++ b/mmgen-addrgen @@ -121,6 +121,8 @@ if invoked_as == "addrgen": opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts) if 'show_hash_presets' in opts: show_hash_presets() +if 'quiet' in opts: g.quiet = True +if 'verbose' in opts: g.verbose = True opts['gen_what'] = gen_what @@ -143,10 +145,10 @@ addr_list = parse_address_list(addr_list_arg) if not addr_list: sys.exit(2) -if not 'quiet' in opts: do_license_msg() +do_license_msg() # Interact with user: -if invoked_as == "keygen" and not 'quiet' in opts: +if invoked_as == "keygen" and not g.quiet: confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue') # Generate data: @@ -163,7 +165,7 @@ addr_data_str = format_addr_data(addr_data, seed_id, opts) # Output data: if 'stdout' in opts: - if invoked_as == "keygen" and not 'quiet' in opts: + if invoked_as == "keygen" and not g.quiet: confirm = True else: confirm = False write_to_stdout(addr_data_str,"secret keys",confirm) diff --git a/mmgen-addrimport b/mmgen-addrimport index 690cd051..8d558f77 100755 --- a/mmgen-addrimport +++ b/mmgen-addrimport @@ -23,9 +23,8 @@ mmgen-addrimport: Import addresses into a bitcoind watching wallet. import sys from mmgen.Opts import * from mmgen.license import * -from mmgen.util import check_infile,confirm_or_exit,msg,msg_r,secs_to_hms,get_lines_from_file +from mmgen.util import * from mmgen.tx import connect_to_bitcoind,parse_addrs_file -from mmgen.bitcoin import verify_addr help_data = { 'prog_name': sys.argv[0].split("/")[-1], @@ -43,6 +42,7 @@ short_opts = "hl:q" long_opts = "help", "addrlist=", "quiet" opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts) +if 'quiet' in opts: g.quiet = True if len(cmd_args) != 1 and not 'addrlist' in opts: msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)") @@ -57,28 +57,27 @@ else: seed_id,addr_data = "",[] if 'addrlist' in opts: - l = get_lines_from_file( - opts['addrlist'],"non-mmgen addresses",remove_comments=True) + l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses", + remove_comments=True) addr_data += [(None,i) for i in l] -msg_r("Validating addresses...") +from mmgen.bitcoin import verify_addr +qmsg_r("Validating addresses...") for i in addr_data: - if not verify_addr(i[1]): + if not verify_addr(i[1],verbose=True): msg("%s: invalid address" % i) sys.exit(2) - -msg("OK") +qmsg("OK") import mmgen.config as g g.http_timeout = 3600 c = connect_to_bitcoind() -if not 'quiet' in opts: - message = """ +m = """ Importing addresses can take a long time (>30 min.) on a low-powered computer. - """ - confirm_or_exit(message, "continue", expect="YES") +""".strip() +confirm_or_exit(m, "continue", expect="YES") import threading import time diff --git a/mmgen-passchg b/mmgen-passchg index ad50803c..aab80550 100755 --- a/mmgen-passchg +++ b/mmgen-passchg @@ -39,6 +39,8 @@ help_data = { -p, --hash-preset p Change scrypt.hash() parameters to preset 'p' (default: '{}') -P, --passwd-file f Get new passphrase from file 'f' +-q, --quiet Suppress warnings; overwrite files without + prompting -v, --verbose Produce more verbose output NOTE: The key ID will change if either the passphrase or hash preset @@ -46,23 +48,24 @@ NOTE: The key ID will change if either the passphrase or hash preset """.format(g.hash_preset) } -short_opts = "hd:HkL:p:P:v" +short_opts = "hd:HkL:p:P:qv" long_opts = "help","outdir=","show_hash_presets","keep_old_passphrase",\ - "label=","hash_preset=","passwd_file=","verbose" + "label=","hash_preset=","passwd_file=","quiet","verbose" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) +if 'quiet' in opts: g.quiet = True +if 'verbose' in opts: g.verbose = True +check_opts(opts,long_opts) if 'show_hash_presets' in opts: show_hash_presets() -check_opts(opts,long_opts) - 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,opts) +label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile) seed_id,key_id = metadata[:2] # Repeat on incorrect pw entry @@ -86,7 +89,7 @@ else: opts['label'] = label # Copy the old label if 'hash_preset' in opts: if hash_preset != opts['hash_preset']: - msg("Hash preset has changed (%s -> %s)" % + qmsg("Hash preset has changed (%s -> %s)" % (hash_preset, opts['hash_preset'])) changed['preset'] = True else: @@ -100,14 +103,14 @@ else: new_passwd = get_new_passphrase("new passphrase", opts) if new_passwd == passwd: - msg("Passphrase is unchanged") + qmsg("Passphrase is unchanged") else: - msg("Passphrase has changed") + qmsg("Passphrase has changed") passwd = new_passwd changed['passwd'] = True if 'preset' in changed or 'passwd' in changed: # Update key ID, salt - msg("Will update salt and key ID") + qmsg("Will update salt and key ID") from hashlib import sha256 from Crypto import Random @@ -115,9 +118,9 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len] key = make_key(passwd, salt, opts['hash_preset']) new_key_id = make_chksum_8(key) - msg("Key ID changed: %s -> %s" % (key_id,new_key_id)) + qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id)) key_id = new_key_id - enc_seed = encrypt_seed(seed, key, opts) + enc_seed = encrypt_seed(seed, key) elif not 'label' in changed: msg("Data unchanged. No file will be written") sys.exit(2) diff --git a/mmgen-pywallet b/mmgen-pywallet index 4684d892..8e53c764 100755 --- a/mmgen-pywallet +++ b/mmgen-pywallet @@ -45,6 +45,7 @@ mmgen-pywallet: Dump contents of a bitcoind wallet to file from mmgen.Opts import * from mmgen.util import msg +import mmgen.config as g from bsddb.db import * import sys, time import json @@ -81,21 +82,18 @@ help_data = { -a, --addrs Dump all addresses (flat list) -K, --keysforaddrs f Dump private keys for addresses listed in file 'f' -P, --passwd-file f Get passphrase from file 'f' --q, --quiet Suppress warnings; overwrite files without prompting -S, --stdout Dump to stdout rather than file """ } -short_opts = "hd:ejkaK:P:qS" +short_opts = "hd:ejkaK:P:S" long_opts = "help","outdir=","echo_passphrase","json","keys","addrs",\ - "keysforaddrs=","passwd_file=","quiet","stdout" + "keysforaddrs=","passwd_file=","stdout" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) - -from mmgen.util import check_infile - check_opts(opts,long_opts) +from mmgen.util import check_infile if len(cmd_args) == 1: check_infile(cmd_args[0]) else: @@ -1683,14 +1681,11 @@ elif 'addrs' in opts: elif 'keysforaddrs' in opts: from mmgen.util import get_lines_from_file - usr_addrs = get_lines_from_file(opts['keysforaddrs'],"addresses") - for addr in usr_addrs: - try: - idx = wallet_addrs.index(addr) - data.append(json_db['keys'][idx]['sec']) - except: - msg("WARNING: Address '%s' not found" % addr) - data,ext,what = sorted(data),"keys","private keys" + usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",remove_comments=True)) + data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs] + ext,what = "keys","private keys" + if len(data) < len(usr_addrs): + msg("Warning: not all requested keys found") len_arg = "%s" % len(wallet_addrs) \ if len(data) == len(wallet_addrs) or ext == "json" \ @@ -1703,8 +1698,7 @@ data = "\n".join(data) + "\n" # Output data if 'stdout' in opts: - if 'addrs' in opts or 'quiet' in opts: confirm = False - else: confirm = True + confirm = False if 'addrs' in opts else True write_to_stdout(data,"secret keys",confirm) elif not sys.stdout.isatty(): write_to_stdout(data,"secret keys",confirm=False) diff --git a/mmgen-txcreate b/mmgen-txcreate index 5a7b53ed..56b98318 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -51,77 +51,82 @@ via an interactive menu. Ages of transactions are approximate based on an average block creation interval of %s minutes. -Addresses on the command line can be Bitcoin addresses or MMGen -addresses in the form : +Addresses on the command line can be Bitcoin addresses or MMGen addresses +of the form :. + +To send all inputs (minus TX fee) to a single output, specify one address +with no amount on the command line. """ % (Decimal(g.tx_fee),g.mins_per_block) } -short_opts = "ha:d:eiqf:" -long_opts = "help","addr_file","outdir=","echo_passphrase","info","quiet",\ - "tx_fee=" +short_opts = "hd:eiqf:" +long_opts = "help","outdir=","echo_passphrase","info","quiet","tx_fee=" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) - +if 'quiet' in opts: g.quiet = True check_opts(opts,long_opts) if g.debug: show_opts_and_cmd_args(opts,cmd_args) +c = connect_to_bitcoind() + if not 'info' in opts: - outputs,addr_files,change_addr = [],[],"" + tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],"" - for a in cmd_args: - if a.split(".")[-1] == g.addrfile_ext: + for a in [i for i in cmd_args if match_ext(i,g.addrfile_ext)]: + if match_ext(a,g.addrfile_ext): check_infile(a) - addr_files.append(a) - elif "," in a: - outputs.append(a) - else: + addr_data.append(parse_addrs_file(a)) + + def mm2btc_addr_proc(c,mmadr,acct_data,addr_data,b2m_map): + btaddr,label = mmgen_addr_to_walletd(c,mmadr,acct_data) + if not btaddr: + btaddr,label = mmgen_addr_to_addr_data(mmadr,addr_data) + b2m_map[btaddr] = mmadr,label + return btaddr + + for a in [i for i in cmd_args if not match_ext(i,g.addrfile_ext)]: + if "," in a: + a1,a2 = a.split(",") + if is_mmgen_addr(a1) or is_btc_addr(a1): + if is_mmgen_addr(a1): + btaddr = mm2btc_addr_proc(c,a1,acct_data,addr_data,b2m_map) + if is_btc_amt(a2): + tx_out[btaddr] = check_btc_amt(a2) + else: + msg("%s: invalid amount in argument '%s'" % (a2,a)) + sys.exit(2) + else: + msg("%s: unrecognized subargument in argument '%s'" % (a1,a)) + sys.exit(2) + elif is_mmgen_addr(a) or is_btc_addr(a): if change_addr: msg("More than one change address specified: %s, %s" % (change_addr, a)) sys.exit(2) - change_addr = a + if is_mmgen_addr(a): + change_addr = mm2btc_addr_proc(c,a,acct_data,addr_data,b2m_map) + else: + change_addr = a + tx_out[change_addr] = 0 + else: + msg("%s: unrecognized argument" % a) + sys.exit(2) - if not outputs: + if not tx_out: msg("At least one output must be specified on the command line") sys.exit(2) - addr_data = [parse_addrs_file(f) for f in addr_files] - tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee - - try: - tx_fee = Decimal(tx_fee) - except: - msg("Invalid transaction fee format: %s" % tx_fee) - sys.exit(2) - + tx_fee = check_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 change_addr: - if ":" in change_addr: - change_addr = mmgen_addr_to_btc_addr(change_addr,addr_data) - else: - check_address(change_addr) - - - tx_out = make_tx_out(outputs,addr_data) - - for i in tx_out.keys(): check_address(i) - for i in tx_out.values(): check_btc_amt(i) - - tx_fee = check_btc_amt(tx_fee) - if g.debug: show_opts_and_cmd_args(opts,cmd_args) -# Begin execution -c = connect_to_bitcoind() - -if not 'quiet' in opts and not 'info' in opts: - do_license_msg(immed=True) +if not 'info' in opts: do_license_msg(immed=True) # Begin test # import mmgen.rpc @@ -131,10 +136,9 @@ if not 'quiet' in opts and not 'info' in opts: us = c.listunspent() if not us: - msg_r(""" + msg(""" No spendable outputs found! Import addresses with balances into your -watch-only wallet using 'mmgen-addrimport' and then re-run this program. -""") +watch-only wallet using 'mmgen-addrimport' and then re-run this program.""") sys.exit(2) # write_to_file("listunspent.json",repr(us)) @@ -146,8 +150,9 @@ 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.values()) -msg("Total amount to spend: %s BTC" % send_amt) +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, @@ -155,17 +160,15 @@ while True: msg("Selected outputs: %s" % " ".join(str(i) for i in sel_nums)) sel_unspent = [unspent[i-1] for i in sel_nums] - lbls = set([verify_mmgen_label( - i.account,return_str=True,check_label_len=True) - for i in sel_unspent]) - lbls.discard("") + mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent]) + mmaddrs.discard("") - if lbls and len(lbls) < len(sel_unspent): - msg(txmsg['mixed_inputs'] % ", ".join(sorted(lbls))) + 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([o.amount for o in sel_unspent])) + total_in = trim_exponent(sum([i.amount for i in sel_unspent])) change = trim_exponent(total_in - (send_amt + tx_fee)) if change >= 0: @@ -175,28 +178,35 @@ while True: else: msg(txmsg['not_enough_btc'] % change) - if change > 0 and not change_addr: - msg(txmsg['throwaway_change'] % (change, total_in-tx_fee)) + 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] -for i in tx_out.keys(): tx_out[i] = float(tx_out[i]) -if change: tx_out[change_addr] = float(change) + if g.debug: print "tx_in:", repr(tx_in) print "tx_out:", repr(tx_out) -tx_hex = c.createrawtransaction(tx_in,tx_out) -msg("Transaction successfully created") +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,pager=pager) + 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): - print_tx_to_file(tx_hex,sel_unspent,send_amt,opts) + print_tx_to_file(tx_hex,sel_unspent,send_amt or change,b2m_map,opts) else: msg("Transaction not saved") diff --git a/mmgen-txsend b/mmgen-txsend index ed168a2d..9a3ce94d 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -44,7 +44,7 @@ short_opts = "hd:q" long_opts = "help","outdir=","quiet" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) - +if 'quiet' in opts: g.quiet = True check_opts(opts,long_opts) if len(cmd_args) == 1: @@ -71,9 +71,9 @@ except: msg("Invalid signed transaction data") sys.exit(3) -if not 'quiet' in opts: do_license_msg() +do_license_msg() -msg("\nSigned transaction file '%s' appears valid" % infile) +qmsg("Signed transaction file '%s' is valid" % infile) warn = "Once this transaction is sent, there's no taking it back!" what = "broadcast this transaction to the network" @@ -86,7 +86,6 @@ c = connect_to_bitcoind() try: tx = c.sendrawtransaction(tx_sig) -# tx = "deadbeef" except: msg("Unable to send transaction") sys.exit(3) diff --git a/mmgen-txsign b/mmgen-txsign index 40857d5e..4daf172c 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -25,7 +25,7 @@ from mmgen.Opts import * from mmgen.license import * import mmgen.config as g from mmgen.tx import * -from mmgen.util import msg +from mmgen.util import msg,qmsg help_data = { 'prog_name': sys.argv[0].split("/")[-1], @@ -36,18 +36,19 @@ 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 information about the transaction and exit --I, --tx_id Display transaction ID and exit +-I, --tx-id Display transaction ID and exit -k, --keys-from-file k Provide additional key data from file 'k' -P, --passwd-file f Get passphrase from file 'f' -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' (comma-separated) + hash preset 'p' -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 +-w, --use-wallet-dat Get keys from a running bitcoind 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. @@ -60,9 +61,13 @@ prompted to enter the data. In cases of transactions with mixed mmgen and non-mmgen inputs, non-mmgen keys must be supplied in a separate file (WIF format, one key per line) -using the '--keys-from-file' option. Alternatively, one may import the -required mmgen keys into the bitcoind wallet.dat and use the -'--force-wallet-dat' option. +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 mmgen keys using 'bitcoind importprivkey'. + +For transaction outputs that are MMGen addresses, MMGen-to-Bitcoin address +mappings are verified. Therefore, seed material for these addresses must +be supplied on the command line. Seed data supplied in files must have the following extensions: wallet: '.{}' @@ -72,78 +77,84 @@ Seed data supplied in files must have the following extensions: """.format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext) } -short_opts = "hd:eiIk:P:qb:msw" +short_opts = "hd:eiIk:P:qVb: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" + "keys_from_file=","passwd_file=","quiet","skip_key_preverify",\ + "from_brain=","from_mnemonic","from_seed","use_wallet_dat" opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts) - +if "quiet" in opts: g.quiet = True check_opts(opts,long_opts) if not infiles: usage(help_data) for i in infiles: check_infile(i) -# 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) +metadata,tx_hex,inputs_data,b2m_map = 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) + view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata) sys.exit(0) -if not 'quiet' in opts: do_license_msg(immed=True) +do_license_msg(immed=True) -msg("Successfully opened transaction file '%s'" % tx_file) +# Are inputs mmgen addresses? +mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]] +other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]] + +keys = get_lines_from_file(opts['keys_from_file'],"key data",remove_comments=True) \ + if 'keys_from_file' in opts else [] + +if other_addrs and not keys and not 'use_wallet_dat' in opts: + missing_keys_errormsg(other_addrs) + sys.exit(2) + +if other_addrs and keys and not 'skip_key_preverify' in opts: + a = [i['address'] for i in other_addrs] + preverify_keys(a, keys) + +seeds = {} +check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts) + +qmsg("Successfully opened transaction file '%s'" % tx_file) 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,metadata,pager=p) + view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p) -# Are inputs mmgen addresses? -mmgen_addrs = [i for i in inputs_data if verify_mmgen_label(i['account'])] -other_addrs = [i for i in inputs_data if not verify_mmgen_label(i['account'])] - -keys = get_lines_from_file(opts['keys_from_file'],"key data") \ - if 'keys_from_file' in opts else [] +sig_data = [ + {"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']} + for i in inputs_data] 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,opts) + ml = [i['account'].split()[0] for i in mmgen_addrs] + keys += get_keys_for_mmgen_addrs(ml,infiles,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_addrs: - if 'use_wallet_dat' in opts: - sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) + if keys: + sig_tx = sign_transaction(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) + sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) if sig_tx['complete']: - msg("Signing completed") - prompt = "Save signed transaction?" + prompt = "OK\nSave 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.") + msg("failed\nSome keys were missing. Transaction could not be signed.") sys.exit(3) diff --git a/mmgen-walletchk b/mmgen-walletchk index 4b36770f..9c5da705 100755 --- a/mmgen-walletchk +++ b/mmgen-walletchk @@ -37,17 +37,20 @@ help_data = { -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' +-q, --quiet Suppress warnings; overwrite files without prompting -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:emP:sSv" +short_opts = "hd:emP:qsSv" long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\ - "passwd_file=","export_seed","stdout","verbose" + "passwd_file=","quiet","export_seed","stdout","verbose" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) +if 'quiet' in opts: g.quiet = True +if 'verbose' in opts: g.verbose = True # Argument sanity checks and processing: check_opts(opts,long_opts) @@ -57,12 +60,12 @@ if len(cmd_args) != 1: usage(help_data) check_infile(cmd_args[0]) if 'export_mnemonic' in opts: - msg("Exporting mnemonic data to file by user request") + qmsg("Exporting mnemonic data to file by user request") if 'export_seed' in opts: - msg("Exporting seed data to file by user request") + qmsg("Exporting seed data to file by user request") seed = get_seed_from_wallet(cmd_args[0], opts) -if seed: msg("Wallet is OK") +if seed: qmsg("Wallet is OK") if 'export_mnemonic' in opts: wl = get_default_wordlist() diff --git a/mmgen-walletgen b/mmgen-walletgen index b1e29a13..ba2e2136 100755 --- a/mmgen-walletgen +++ b/mmgen-walletgen @@ -45,10 +45,11 @@ help_data = { -p, --hash-preset p Use scrypt.hash() parameters from preset 'p' (default: '{}') -P, --passwd-file f Get passphrase from file 'f' --q, --quiet Suppress warnings; overwrite files without +-q, --quiet Produce quieter output; overwrite files without prompting -u, --usr-randlen n Get 'n' characters of randomness from the user (default: {}) +-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 @@ -92,13 +93,14 @@ in all future invocations with that passphrase. ) } -short_opts = "hd:eHl:L:p:P:qu:b:ms" +short_opts = "hd:eHl:L:p:P:qu:vb: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" + "verbose","from_brain=","from_mnemonic","from_seed" opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) - +if 'quiet' in opts: g.quiet = True +if 'verbose' in opts: g.verbose = True if 'show_hash_presets' in opts: show_hash_presets() check_opts(opts,long_opts) @@ -108,15 +110,23 @@ 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.seed_ext, g.mn_ext, g.brain_ext + 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 -if not 'quiet' in opts: do_license_msg() +do_license_msg() -msg_r("Acquiring random data from your computer...") +qmsg_r("Acquiring random data from your computer...") from time import sleep sleep(0.25) @@ -129,12 +139,12 @@ except: msg("FAILED\nUnable to generate random numbers. Exiting") sys.exit(2) -msg("OK") +qmsg("OK") if g.debug: display_os_random_data(os_rand_data) usr_keys,key_timings = get_random_data_from_user(opts) -msg("") +qmsg("") if g.debug: display_user_random_data(usr_keys,key_timings) @@ -143,7 +153,8 @@ usr_rand_data = sha256(usr_keys).digest() + \ for i in 'from_mnemonic','from_brain','from_seed': if infile or (i in opts): - seed = get_seed_retry(infile,opts); break + seed = get_seed_retry(infile,opts) + qmsg(""); break else: # Truncate random data for smaller seed lengths seed = os_rand_data[0] + usr_rand_data @@ -152,9 +163,7 @@ else: salt = os_rand_data[1] + usr_rand_data salt = sha256(salt).digest()[:g.salt_len] -if not 'quiet' in opts: - msg(""" -Now you must choose a passphrase to encrypt the seed with. A key will be +qmsg("""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. @@ -164,6 +173,6 @@ 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, opts) +enc_seed = encrypt_seed(seed, key) write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts) diff --git a/mmgen/Opts.py b/mmgen/Opts.py index 7a877f2f..3bbc195d 100755 --- a/mmgen/Opts.py +++ b/mmgen/Opts.py @@ -18,7 +18,7 @@ import sys, getopt import mmgen.config as g -from mmgen.util import msg +from mmgen.util import msg,check_infile def usage(hd): print "USAGE: %s %s" % (hd['prog_name'], hd['usage']) @@ -117,7 +117,9 @@ def check_opts(opts,long_opts): for ch in list(label): if ch not in g.wallet_label_symbols: - msg("'%s': illegal character in label" % ch) + msg(""" +"%s": illegal character in label. Only ASCII characters are permitted. +""".strip() % ch) sys.exit(1) elif opt == 'from_brain': try: diff --git a/mmgen/addr.py b/mmgen/addr.py index a6b43475..e3510fd1 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -24,6 +24,7 @@ from hashlib import sha256, sha512 from binascii import hexlify, unhexlify from mmgen.bitcoin import numtowif +from mmgen.util import msg,qmsg,qmsg_r import mmgen.config as g def test_for_keyconv(): @@ -47,27 +48,12 @@ faster address generation. def generate_addrs(seed, addrnums, opts): - """ - generate_addresses(start, end, seed, opts) => None - - Generate a Bitcoin address or addresses end based on a seed, optionally - outputting secret keys - - The 'keyconv' utility will be used for address generation if installed. - Otherwise an internal function is used - - Supported options: - print_secret, no_addresses, no_keyconv, gen_what - - Addresses are returned in a list of dictionaries with the following keys: - num, sec, wif, addr - """ if not 'no_addresses' in opts: if 'no_keyconv' in opts or test_for_keyconv() == False: - sys.stderr.write("Using (slow) internal ECDSA library for address generation\n") + msg("Using (slow) internal ECDSA library for address generation") from mmgen.bitcoin import privnum2addr - keyconv = "" + keyconv = False else: from subprocess import Popen, PIPE keyconv = "keyconv" @@ -75,12 +61,13 @@ def generate_addrs(seed, addrnums, opts): a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[] while a: - seed = sha512(seed).digest(); i += 1 # round /i/ + seed = sha512(seed).digest() + i += 1 # round /i/ if i < a[0]: continue a.pop(0) - sys.stderr.write("\rGenerating %s %s (%s of %s)" % + qmsg_r("\rGenerating %s %s (%s of %s)" % (opts['gen_what'], i, t_addrs-len(a), t_addrs)) # Secret key is double sha256 of seed hash round /i/ @@ -110,7 +97,7 @@ def generate_addrs(seed, addrnums, opts): import re w = re.sub('e*s$','',w) - sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs, w, " "*15)) + qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15)) return out @@ -153,10 +140,9 @@ def format_addr_data(addrlist, seed_chksum, opts): # 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(g.proj_name.capitalize(),g.max_addr_label_len, - "', '".join(g.addr_label_punc[0:-1]), g.addr_label_punc[-1]).strip() +# The label may contain printable ASCII symbols. +""".strip().format(g.proj_name_cap,g.max_addr_label_len) + data = [] if not 'stdout' in opts: data.append(header + "\n") data.append("%s {" % seed_chksum.upper()) @@ -210,7 +196,7 @@ def write_addr_data_to_file(seed, data, addr_list, opts): else: ext = "akeys" if 'b16' in opts: ext = ext.replace("keys","xkeys") - from mmgen.util import write_to_file, make_chksum_8, msg + from mmgen.util import write_to_file, make_chksum_8 outfile = "{}[{}].{}".format( make_chksum_8(seed), fmt_addr_list(addr_list), diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index d9ab2ce2..71d677f4 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution # Copyright (C) 2013 by philemon # # This program is free software: you can redistribute it and/or modify @@ -61,15 +61,10 @@ def pubhex2addr(pubhex): pubkey = int(step2 + step4[:8], 16) return "1" + ("1" * extra_ones) + _numtob58(pubkey) -def privnum2addr(numpriv): - pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1) - pubkey = hexlify(pko.get_verifying_key().to_string()) - return pubhex2addr('04'+pubkey) - -def verify_addr(addr): +def verify_addr(addr,verbose=False): if addr[0] != "1": - print "%s: Invalid address" % addr + if verbose: print "%s: Invalid address" % addr return False num = _b58tonum(addr[1:]) @@ -80,7 +75,7 @@ def verify_addr(addr): step2 = sha256(step1).hexdigest() if step2[:8] != addr_hex[40:]: - print "Invalid checksum in address %s" % ("1" + addr) + if verbose: print "Invalid checksum in address %s" % ("1" + addr) return False return True @@ -115,8 +110,7 @@ def numtowif(numpriv): key = step1 + step3[:8] return _numtob58(int(key,16)) - -# The following are mmgen internal (non-bitcoin) b58 functions +# The following are MMGen internal (non-Bitcoin) b58 functions # Drop-in replacements for b64encode() and b64decode(): # (well, not exactly: they yield numeric but not bytewise equivalence) @@ -159,19 +153,36 @@ def b58decode_pad(s): return _b58_pad(s, a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers") +# Compressed address support: -################### FUNCTIONS UNUSED BY MMGEN: ################### +def wiftohex(wifpriv,compressed=False): + idx = 68 if compressed else 66 + num = _b58tonum(wifpriv) + if num == False: return False + key = hex(num)[2:].rstrip('L') + if compressed and key[66:68] != '01': return False + round1 = sha256(unhexlify(key[:idx])).digest() + round2 = sha256(round1).hexdigest() + return key[2:66] if (key[:2] == '80' and key[idx:] == round2[:8]) else False -# To check validity, recode with numtowif() +def hextowif(hexpriv,compressed=False): + step1 = '80' + hexpriv + ('01' if compressed else '') + step2 = sha256(unhexlify(step1)).digest() + step3 = sha256(step2).hexdigest() + key = step1 + step3[:8] + return _numtob58(int(key,16)) + +def privnum2addr(numpriv,compressed=False): + pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1) + pubkey = hexlify(pko.get_verifying_key().to_string()) + if compressed: + p = '03' if pubkey[-1] in "13579bdf" else '02' + return pubhex2addr(p+pubkey[:64]) + else: + return pubhex2addr('04'+pubkey) + +# Used only in test suite. To check validity, recode with numtowif() def wiftonum(wifpriv): num = _b58tonum(wifpriv) if num == False: return False return (num % (1<<288)) >> 32 - -def wiftohex(wifpriv): - num = _b58tonum(wifpriv) - if num == False: return False - key = hex(num)[2:].rstrip('L') - round1 = sha256(unhexlify(key[:66])).digest() - round2 = sha256(round1).hexdigest() - return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False diff --git a/mmgen/config.py b/mmgen/config.py index ee6bfa9f..8844cd1b 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -18,12 +18,15 @@ """ config.py: Constants and configuration options for the mmgen suite """ +quiet,verbose = False,False +min_screen_width = 80 from decimal import Decimal tx_fee = Decimal("0.001") max_tx_fee = Decimal("0.1") proj_name = "mmgen" +proj_name_cap = "MMGen" wallet_ext = "mmdat" seed_ext = "mmseed" @@ -71,12 +74,16 @@ hash_presets = { '6': [17, 8, 20], } +mmgen_idx_max_digits = 7 + from string import ascii_letters, digits -addr_label_punc = ".","_",",","-"," " -addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc -max_addr_label_len = 16 +addr_label_symbols = tuple([chr(i) for i in range(0x20,0x7f)]) +max_addr_label_len = 32 -wallet_label_punc = ".", "_", " " -wallet_label_symbols = tuple(ascii_letters + digits) + wallet_label_punc +wallet_label_symbols = addr_label_symbols max_wallet_label_len = 32 + +#addr_label_punc = ".","_",",","-"," ","(",")" +#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc +#wallet_label_punc = addr_label_punc diff --git a/mmgen/license.py b/mmgen/license.py index 8b02b61f..afc54276 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -585,6 +585,10 @@ copy of the Program in return for a fee. } def do_license_msg(immed=False): + + import mmgen.config as g + if g.quiet: return + msg(gpl['warning']) prompt = "%s " % gpl['prompt'].strip() diff --git a/mmgen/rpc/proxy.py b/mmgen/rpc/proxy.py index ec0e436d..62c98f04 100755 --- a/mmgen/rpc/proxy.py +++ b/mmgen/rpc/proxy.py @@ -106,7 +106,7 @@ class AuthServiceProxy(object): except: from mmgen.util import msg import sys - msg("\nUnable to connect to bitcoind.") + msg("Unable to connect to bitcoind") sys.exit(2) httpresp = self.__conn.getresponse() diff --git a/mmgen/term.py b/mmgen/term.py new file mode 100755 index 00000000..4781f894 --- /dev/null +++ b/mmgen/term.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C) 2013 by philemon +# +# 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 . +""" +term.py: Terminal-handling routines for the mmgen suite +""" + +import sys, os, struct + +def msg(s): sys.stderr.write(s + "\n") +def msg_r(s): sys.stderr.write(s) + +def _kb_hold_protect_unix(): + + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + tty.setcbreak(fd) + + 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: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + + +def _get_keypress_unix(prompt="",immed_chars=""): + + msg_r(prompt) + timeout = float(0.3) + + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + tty.setcbreak(fd) + + try: + while True: + select([sys.stdin], [], [], False) + ch = sys.stdin.read(1) + 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 + second_key = select([sys.stdin], [], [], timeout)[0] + if second_key: continue + else: return ch + except KeyboardInterrupt: + msg("\nUser interrupt") + sys.exit(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + + +def _kb_hold_protect_mswin(): + + timeout = float(0.5) + + try: + while True: + hit_time = time.time() + while True: + if msvcrt.kbhit(): + msvcrt.getch() + break + if float(time.time() - hit_time) > timeout: + return + except KeyboardInterrupt: + msg("\nUser interrupt") + sys.exit(1) + + +def _get_keypress_mswin(prompt="",immed_chars=""): + + msg_r(prompt) + timeout = float(0.5) + + try: + while True: + if msvcrt.kbhit(): + ch = msvcrt.getch() + + if ord(ch) == 3: raise KeyboardInterrupt + + 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 + + hit_time = time.time() + + while 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(): + + def ioctl_GWINSZ(fd): + try: + import fcntl + import termios + cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + return cr + except: + pass + + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + + if not cr: + try: + cr = (os.environ['LINES'], os.environ['COLUMNS']) + except: + return 80,25 + + return int(cr[1]), int(cr[0]) + + +def _get_terminal_size_mswin(): + try: + from ctypes import windll, create_string_buffer + # stdin handle is -10 + # stdout handle is -11 + # stderr handle is -12 + h = windll.kernel32.GetStdHandle(-12) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (bufx, bufy, curx, cury, wattr, + left, top, right, bottom, + maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + sizex = right - left + 1 + sizey = bottom - top + 1 + return sizex, sizey + except: + return 80,25 + +try: + import tty, termios + from select import select + get_char = _get_keypress_unix + kb_hold_protect = _kb_hold_protect_unix + get_terminal_size = _get_terminal_size_linux +except: + try: + import msvcrt, time + get_char = _get_keypress_mswin + kb_hold_protect = _kb_hold_protect_mswin + get_terminal_size = _get_terminal_size_mswin + except: + if not sys.platform.startswith("linux") \ + and not sys.platform.startswith("win"): + msg("Unsupported platform: %s" % sys.platform) + msg("This program currently runs only on Linux and Windows") + else: + msg("Unable to set terminal mode") + sys.exit(2) + +if __name__ == "__main__": + print "columns: {}, rows: {}".format(*get_terminal_size()) diff --git a/mmgen/tests/bitcoin.py b/mmgen/tests/bitcoin.py index 1c19e82d..6abb908c 100755 --- a/mmgen/tests/bitcoin.py +++ b/mmgen/tests/bitcoin.py @@ -19,18 +19,19 @@ bitcoin.py: Test suite for mmgen.bitcoin module """ -from mmgen.bitcoin import * -from mmgen.util import msg -from test import * +import mmgen.bitcoin as b +from mmgen.util import msg +from mmgen.tests.test import * +from binascii import hexlify, unhexlify import sys def b58_randenc(): r = get_random(24) - r_enc = b58encode(r) + r_enc = b.b58encode(r) print "Data (hex): %s" % hexlify(r) print "Base 58: %s" % r_enc - r_dec = b58decode(r_enc) + r_dec = b.b58decode(r_enc) print "Decoded data: %s" % hexlify(r_dec) if r_dec != r: print "ERROR! Decoded data doesn't match original" @@ -55,9 +56,8 @@ def keyconv_compare_randloop(loops, quiet=False): else: print "%s iterations completed" % i - except: - print "\nUser interrupt" - + except KeyboardInterrupt: + msg("\nUser interrupt") def keyconv_compare(wif,quiet=False): do_msg = nomsg if quiet else msg @@ -69,7 +69,7 @@ def keyconv_compare(wif,quiet=False): print "Error with execution of keyconv" sys.exit(3) kc_addr = dict([j.split() for j in p.stdout.readlines()])['Address:'] - addr = privnum2addr(wiftonum(wif)) + addr = b.privnum2addr(b.wiftonum(wif)) do_msg("Address (mmgen): %s" % addr) do_msg("Address (keyconv): %s" % kc_addr) if (kc_addr != addr): @@ -113,13 +113,13 @@ def numtowif_rand(quiet=False): def strtob58(s,quiet=False): print "Input: %s" % s - s_enc = b58encode(s) + s_enc = b.b58encode(s) print "Encoded data: %s" % s_enc - s_dec = b58decode(s_enc) + s_dec = b.b58decode(s_enc) print "Decoded data: %s" % s_dec test_equality(s,s_dec,[""],quiet) -def hextob58(s_in,f_enc=b58encode, f_dec=b58decode, quiet=False): +def hextob58(s_in,f_enc=b.b58encode, f_dec=b.b58decode, quiet=False): do_msg = nomsg if quiet else msg do_msg("Input: %s" % s_in) s_bin = unhexlify(s_in) @@ -129,7 +129,7 @@ def hextob58(s_in,f_enc=b58encode, f_dec=b58decode, quiet=False): do_msg("Recoded data: %s" % s_dec) test_equality(s_in,s_dec,["0"],quiet) -def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False): +def b58tohex(s_in,f_dec=b.b58decode, f_enc=b.b58encode,quiet=False): print "Input: %s" % s_in s_dec = f_dec(s_in) print "Decoded data: %s" % hexlify(s_dec) @@ -138,25 +138,25 @@ def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False): test_equality(s_in,s_enc,["1"],quiet) def hextob58_pad(s_in, quiet=False): - hextob58(s_in,f_enc=b58encode_pad, f_dec=b58decode_pad, quiet=quiet) + hextob58(s_in,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet) def b58tohex_pad(s_in, quiet=False): - b58tohex(s_in,f_dec=b58decode_pad, f_enc=b58encode_pad, quiet=quiet) + 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=b58encode_pad, f_dec=b58decode_pad, quiet=quiet) + hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet) if not quiet: print if not i % 100 and quiet: sys.stderr.write("\riteration: %i " % i) sys.stderr.write("\r%s iterations completed\n" % i) - except: - print "User interrupt" + except KeyboardInterrupt: + msg("\nUser interrupt") -def test_wiftohex(s_in,f_dec=wiftohex,f_enc=numtowif): +def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif): print "Input: %s" % s_in s_dec = f_dec(s_in) print "Decoded data: %s" % s_dec @@ -170,7 +170,31 @@ def hextosha256(s_in): def pubhextoaddr(s_in): print "Entered data: %s" % s_in - s_enc = pubhex2addr(s_in) + s_enc = b.pubhex2addr(s_in) + print "Encoded data: %s" % s_enc + +def hextowif_comp(s_in): + print "Entered data: %s" % s_in + s_enc = b.hextowif(s_in,compressed=True) + print "Encoded data: %s" % s_enc + s_dec = b.wiftohex(s_enc,compressed=True) + print "Decoded data: %s" % s_dec + +def wiftohex_comp(s_in): + print "Entered data: %s" % s_in + s_enc = b.wiftohex(s_in,compressed=True) + print "Encoded data: %s" % s_enc + s_dec = b.hextowif(s_enc,compressed=True) + print "Decoded data: %s" % s_dec + +def privhextoaddr_comp(hexpriv): + print b.privnum2addr(int(hexpriv,16),compressed=True) + +def wiftoaddr_comp(s_in): + print "Entered data: %s" % s_in + s_enc = b.wiftohex(s_in,compressed=True) + print "Encoded data: %s" % s_enc + s_enc = b.privnum2addr(int(s_enc,16),compressed=True) print "Encoded data: %s" % s_enc tests = { @@ -188,6 +212,10 @@ tests = { "hextosha256": ['hexnum [str]','quiet [bool=False]'], "hextowiftopubkey": ['hexnum [str]','quiet [bool=False]'], "pubhextoaddr": ['hexnum [str]','quiet [bool=False]'], + "hextowif_comp": ['hexnum [str]'], + "wiftohex_comp": ['wif [str]'], + "privhextoaddr_comp": ['hexnum [str]'], + "wiftoaddr_comp": ['wif [str]'], } diff --git a/mmgen/tests/test.py b/mmgen/tests/test.py index 3125d257..f406b355 100755 --- a/mmgen/tests/test.py +++ b/mmgen/tests/test.py @@ -20,8 +20,8 @@ test.py: Shared routines for mmgen test suite """ import sys -from mmgen.util import msg +def msg(s): print s def nomsg(s): pass def test_equality(num_in,num_out,wl,quiet=False): diff --git a/mmgen/tx.py b/mmgen/tx.py index 398a5817..9232e7ab 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -29,9 +29,7 @@ txmsg = { 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)", 'throwaway_change': """ ERROR: This transaction produces change (%s BTC); however, no change -address was specified. Total inputs - transaction fee = %s BTC. -To create a valid transaction with no change address, send this sum to the -specified recipient address. +address was specified. """.strip(), 'mixed_inputs': """ NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs, @@ -73,30 +71,36 @@ def trim_exponent(n): return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize() -def check_address(rcpt_address): - from mmgen.bitcoin import verify_addr - if not verify_addr(rcpt_address): - sys.exit(3) - -def check_btc_amt(send_amt): +def is_btc_amt(amt): from decimal import Decimal try: - retval = Decimal(send_amt) + ret = Decimal(amt) except: - msg("%s: Invalid amount" % send_amt) - sys.exit(3) + msg("%s: Invalid amount" % amt) + return False if g.debug: - print "Decimal(amt): %s\nAs tuple: %s" % (send_amt,repr(retval.as_tuple())) + print "Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple())) - if retval.as_tuple()[-1] < -8: - msg("%s: Too many decimal places in amount" % send_amt) + if ret.as_tuple()[-1] < -8: + msg("%s: Too many decimal places in amount" % amt) + return False + + if ret == 0: + msg("Requested zero BTC amount") + return False + + return trim_exponent(ret) + +def check_btc_amt(amt): + ret = is_btc_amt(amt) + if ret: + return ret + else: sys.exit(3) - return trim_exponent(retval) - def get_bitcoind_cfg_options(cfg_keys): @@ -108,7 +112,7 @@ def get_bitcoind_cfg_options(cfg_keys): data_dir = r"Application Data\Bitcoin" cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf") else: - msg("Neither $HOME nor %HOMEPATH% is set") + msg("Neither $HOME nor %HOMEPATH% are set") msg("Don't know where to look for 'bitcoin.conf'") sys.exit(3) @@ -135,17 +139,16 @@ def get_bitcoind_cfg_options(cfg_keys): return cfg -def print_tx_to_file(tx,sel_unspent,send_amt,opts): +def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts): tx_id = make_chksum_6(unhexlify(tx)).upper() outfile = "tx_%s[%s].raw" % (tx_id,send_amt) if 'outdir' in opts: outfile = "%s/%s" % (opts['outdir'], outfile) metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp()) - sig_data = [{"txid":i.txid,"vout":i.vout,"scriptPubKey":i.scriptPubKey} - for i in sel_unspent] data = "%s\n%s\n%s\n%s\n" % ( - metadata, tx, repr(sig_data), - repr([i.__dict__ for i in sel_unspent]) + metadata, tx, + repr([i.__dict__ for i in sel_unspent]), + repr(b2m_map) ) write_to_file(outfile,data,confirm=False) msg("Transaction data saved to file '%s'" % outfile) @@ -176,17 +179,11 @@ def format_unspent_outputs_for_printing(out,sort_info,total): "Amount (BTC)","Age (days)", "Comment")] for n,i in enumerate(out): - if verify_mmgen_label(i.account): - s = i.account.split(None,1) - mmid,cmt = s[0],(s[1] if len(s) == 2 else "") - else: - mmid,cmt = "",i.account - addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address tx = " " * 63 + "=" \ if i.skip == "txid" and "grouped" in sort_info else str(i.txid) - s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,mmid,i.amt,i.days,cmt) + s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,i.mmid,i.amt,i.days,i.label) pout.append(s.rstrip()) return \ @@ -197,23 +194,20 @@ def format_unspent_outputs_for_printing(out,sort_info,total): def sort_and_view(unspent): - def s_amt(a,b): return cmp(a.amount,b.amount) - def s_txid(a,b): - return cmp("%s %03s" % (a.txid,a.vout), "%s %03s" % (b.txid,b.vout)) - def s_addr(a,b): return cmp(a.address,b.address) - def s_age(a,b): return cmp(b.confirmations,a.confirmations) - def s_mmgen(a,b): return cmp(a.account,b.account) + def s_amt(i): return i.amount + def s_txid(i): return "%s %03s" % (i.txid,i.vout) + def s_addr(i): return i.address + def s_age(i): return i.confirmations + def s_mmgen(i): return i.account - fs = " %-4s %-11s %-2s %-34s %-13s %-s" sort,group,show_mmaddr,reverse = "",False,False,False total = trim_exponent(sum([i.amount for i in unspent])) hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s" - table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)", "Age(days)") options_msg = """ Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr -Format options: [g]roup, show [m]mgen addr +Display options: [g]roup, show [m]mgen addr, r[e]draw screen """.strip() prompt = \ "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): " @@ -222,57 +216,82 @@ Format options: [g]roup, show [m]mgen addr print_to_file_msg = "" msg("") + from mmgen.term import get_terminal_size + + max_acct_len = max([len(i.account) for i in unspent]) + while True: - out = deepcopy(unspent) - for i in out: i.skip = "" + cols = get_terminal_size()[0] + if cols < g.min_screen_width: + msg("mmgen-txcreate requires a screen at least %s characters wide" % + g.min_screen_width) + sys.exit(2) + + addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46) + tx_w = max(11,min(64, cols-addr_w-32)) + fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s" + table_hdr = fs % ("Num","TX id Vout","","Address", + "Amount (BTC)","Age(d)") + + unsp = deepcopy(unspent) + for i in unsp: i.skip = "" if group and (sort == "address" or sort == "txid"): - for n in range(len(out)-1): - a,b = out[n],out[n+1] + for n in range(len(unsp)-1): + a,b = unsp[n],unsp[n+1] if sort == "address" and a.address == b.address: b.skip = "addr" elif sort == "txid" and a.txid == b.txid: b.skip = "txid" - for i in out: + for i in unsp: amt = str(trim_exponent(i.amount)) lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt) i.amt = " "*lfill + amt i.days = int(i.confirmations * g.mins_per_block / (60*24)) + i.mmid,i.label = parse_mmgen_label(i.account) + if i.skip == "addr": i.addr = "|" + "." * 33 else: - if show_mmaddr: - if verify_mmgen_label(i.account): - i.addr = "%s.. %s" % (i.address[:4],i.account) - else: - i.addr = i.address + if show_mmaddr and i.mmid: + acct_w = min(max_acct_len, max(24,int(addr_w-10))) + btaddr_w = addr_w - acct_w - 1 + + dots = ".." if btaddr_w < len(i.address) else "" + + i.addr = "%s%s %s" % ( + i.address[:btaddr_w-len(dots)], + dots, + i.account[:acct_w]) else: i.addr = i.address - i.tx = " |..." if i.skip == "txid" else i.txid[:8]+"..." + dots = "..." if tx_w < 64 else "" + i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \ + else i.txid[:tx_w-len(dots)]+dots sort_info = ["reverse"] if reverse else [] sort_info.append(sort if sort else "unsorted") if group and (sort == "address" or sort == "txid"): sort_info.append("grouped") - output = [hdr_fmt % (" ".join(sort_info), total), table_hdr] + out = [hdr_fmt % (" ".join(sort_info), total), table_hdr] - for n,i in enumerate(out): - output.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days)) + for n,i in enumerate(unsp): + out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days)) - msg("\n".join(output) +"\n\n" + print_to_file_msg + options_msg) + msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg) print_to_file_msg = "" - immed_chars = "qpPtadArMgm" + immed_chars = "atdAMrgmeqpvw" skip_prompt = False while True: reply = get_char(prompt, immed_chars=immed_chars) - if reply == 'a': unspent.sort(s_amt); sort = "amount" - elif reply == 't': unspent.sort(s_txid); sort = "txid" - elif reply == 'd': unspent.sort(s_addr); sort = "address" - elif reply == 'A': unspent.sort(s_age); sort = "age" + if reply == 'a': unspent.sort(key=s_amt); sort = "amount" + elif reply == 't': unspent.sort(key=s_txid); sort = "txid" + elif reply == 'd': unspent.sort(key=s_addr); sort = "address" + elif reply == 'A': unspent.sort(key=s_age); sort = "age" elif reply == 'M': unspent.sort(s_mmgen) sort = "mmgen" @@ -282,17 +301,18 @@ Format options: [g]roup, show [m]mgen addr reverse = False if reverse else True elif reply == 'g': group = False if group else True elif reply == 'm': show_mmaddr = False if show_mmaddr else True + elif reply == 'e': pass elif reply == 'q': pass elif reply == 'p': - data = format_unspent_outputs_for_printing(out,sort_info,total) + data = format_unspent_outputs_for_printing(unsp,sort_info,total) outfile = "listunspent[%s].out" % ",".join(sort_info) write_to_file(outfile, data) print_to_file_msg = "Data written to '%s'\n\n" % outfile elif reply == 'v': - do_pager("\n".join(output)) + do_pager("\n".join(out)) continue elif reply == 'w': - data = format_unspent_outputs_for_printing(out,sort_info,total) + data = format_unspent_outputs_for_printing(unsp,sort_info,total) do_pager(data) continue else: @@ -307,31 +327,19 @@ Format options: [g]roup, show [m]mgen addr return tuple(unspent) -def verify_mmgen_label(s,return_str=False,check_label_len=False): +def parse_mmgen_label(s,check_label_len=False): - fail = "" if return_str else False - success = s if return_str else True + if not s: return "","" - if not s: return fail + try: w1,w2 = s.split(None,1) + except: w1,w2 = s,"" - try: - mminfo,comment = s.split(None,1) - except: - mminfo,comment = s,None - - if mminfo[8] != ':': return fail - for i in mminfo[:8]: - if not i in "01234567890ABCDEF": return fail - for i in mminfo[9:]: - if not i in "0123456789": return fail - - if check_label_len and comment: - check_addr_comment(comment) - - return success + if not is_mmgen_addr(w1): return "",w1 + if check_label_len: check_addr_label(w2) + return w1,w2 -def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False): +def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False): td = c.decoderawtransaction(tx_hex) @@ -347,39 +355,51 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False): if j['txid'] == i['txid'] and j['vout'] == i['vout']: days = int(j['confirmations'] * g.mins_per_block / (60*24)) total_in += j['amount'] - out += (" " + """ -%-2s tx,vout: %s,%s - address: %s - ID/label: %s - amount: %s BTC - confirmations: %s (around %s days) -""".strip() % - (n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True), - trim_exponent(j['amount']),j['confirmations'],days)+"\n\n") + addr = j['address'] + + if j['account']: + tmp = j['account'].split(None,1) + mmid,label = tmp if len(tmp) == 2 else (tmp[0],"") + label = label or "" + else: + mmid,label = "","" + + mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else "" + + for d in ( + (n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])), + ("", "address:", addr + mmid_str), + ("", "label:", label), + ("", "amount:", "%s BTC" % trim_exponent(j['amount'])), + ("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days)) + ): + if d[2]: out += ("%3s %-8s %s\n" % d) + out += "\n" + break - - out += "Total input: %s BTC\n\n" % trim_exponent(total_in) - total_out = 0 out += "Outputs:\n\n" for n,i in enumerate(td['vout']): + addr = i['scriptPubKey']['addresses'][0] + mmid,label = b2m_map[addr] if addr in b2m_map else ("","") + mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else "" total_out += i['value'] - out += (" " + """ -%-2s address: %s - amount: %s BTC -""".strip() % ( - n, - i['scriptPubKey']['addresses'][0], - trim_exponent(i['value'])) - + "\n\n") + for d in ( + (n+1, "address:", addr + mmid_str), + ("", "label:", label), + ("", "amount:", trim_exponent(i['value'])) + ): + if d[2]: out += ("%3s %-8s %s\n" % d) + out += "\n" + + out += "Total input: %s BTC\n" % trim_exponent(total_in) out += "Total output: %s BTC\n" % trim_exponent(total_out) out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out) - if pager: do_pager(out+"\n") + if pager: do_pager(out) else: msg("\n"+out) - def parse_tx_data(tx_data,infile): if len(tx_data) != 4: @@ -396,20 +416,28 @@ def parse_tx_data(tx_data,infile): except: msg(err_fmt % "hex data") sys.exit(2) + else: + if not tx_data: + msg("Transaction is empty!") + sys.exit(2) try: - sig_data = eval(tx_data[2]) - except: - msg(err_fmt % "signature data") - sys.exit(2) - - try: - inputs_data = eval(tx_data[3]) + inputs_data = eval(tx_data[2]) except: msg(err_fmt % "inputs data") sys.exit(2) + else: + if not inputs_data: + msg("Transaction has no inputs!") + sys.exit(2) - return tx_data[0].split(),tx_data[1],sig_data,inputs_data + try: + map_data = eval(tx_data[3]) + except: + msg(err_fmt % "mmgen to btc address map data") + sys.exit(2) + + return tx_data[0].split(),tx_data[1],inputs_data,map_data def select_outputs(unspent,prompt): @@ -430,8 +458,83 @@ def select_outputs(unspent,prompt): return selected +def is_mmgen_seed(s): + import re + return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s) -def mmgen_addr_to_btc_addr(m,addr_data): +def is_mmgen_num(s): + import re + return len(s) <= g.mmgen_idx_max_digits \ + and re.match(r"^[123456789]+[0123456789]*$",s) + +def is_mmgen_addr(s): + import re + return len(s) > 9 and s[8] == ':' \ + and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \ + and len(s[9:]) <= g.mmgen_idx_max_digits \ + and re.match(r"^[123456789]+[0123456789]*$",s[9:]) + +def is_btc_addr(s): + from mmgen.bitcoin import verify_addr + return verify_addr(s) + + +def btc_addr_to_mmgen_addr(btc_addr,b2m_map): + if btc_addr in b2m_map: + return b2m_map[btc_addr] + return "","" + + +def mmgen_addr_to_walletd(c,mmaddr,acct_data): + + # We don't want to create a new object, so we'll use append() + if not acct_data: + for i in c.listaccounts(): + acct_data.append(i) + + for a in acct_data: + if not a: continue + try: + w1,w2 = a.split(None,1) + except: + w1,w2 = a,"" + if w1 == mmaddr: + acct = a + break + else: + return "","" + + alist = c.getaddressesbyaccount(acct) + + if len(alist) != 1: + msg(""" +ERROR: More than one address found for account: "%s". +The tracking "wallet.dat" file appears to have been altered by a non-%s +program. Please restore "wallet.dat" from a backup or create a new wallet +and re-import your addresses. +""".strip() % (acct,g.proj_name_cap)) + sys.exit(3) + + return alist[0],w2 + + +def mmgen_addr_to_addr_data(m,addr_data): + + no_data_msg = """ +No data found for MMgen address '%s'. Please import this address into +your tracking wallet, or supply an address file for it on the command line. +""".strip() % m + warn_msg = """ +Warning: no data for address '%s' exists in the wallet, so it was +taken from the user-supplied address file. You're strongly advised to +import this address into your tracking wallet before proceeding with +this transaction. The address will not be tracked until you do so. +""".strip() % m + fail_msg = """ +No data found for MMgen address '%s' in either wallet or supplied +address file. Please import this address into your tracking wallet, or +supply an address file for it on the command line. +""".strip() % m ID,num = m.split(":") from binascii import unhexlify @@ -442,68 +545,66 @@ def mmgen_addr_to_btc_addr(m,addr_data): except: pass else: if not addr_data: - msg("Address data must be supplied for MMgen address '%s'" % m) + msg(no_data_msg) sys.exit(2) for i in addr_data: if ID == i[0]: for j in i[1]: if j[0] == num: - return j[1] - msg("MMgen address '%s' not found in supplied address data" % m) + msg(warn_msg) + if not user_confirm("Continue anyway?"): + sys.exit(1) + return j[1],(j[2] if len(j) == 3 else "") + msg(fail_msg) sys.exit(2) msg("Invalid format: %s" % m) sys.exit(3) +def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts): + in_maplist = [(i['account'].split()[0],i['address']) + for i in inputs_data if i['account'] + and is_mmgen_addr(i['account'].split()[0])] + out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()] -def make_tx_out(tx_arg,addr_data): + for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"): + if not maplist: continue + qmsg("Checking MMGen -> BTC address mappings for %s" % label) + mmaddrs = [i[0] for i in maplist] + from copy import deepcopy + pairs = get_keys_for_mmgen_addrs(mmaddrs, + deepcopy(infiles),seeds,opts,gen_pairs=True) + for a,b in zip(sorted(pairs),sorted(maplist)): + if a != b: + msg(""" +MMGen -> BTC address mappings differ! +In transaction: %s +Generated from seed: %s + """.strip() % (" ".join(a)," ".join(b))) + sys.exit(3) - tx = {} - for i in tx_arg: - addr,amt = i.split(",") - - if ":" in addr: - addr = mmgen_addr_to_btc_addr(addr,addr_data) - else: - check_address(addr) - - try: tx[addr] = amt - except: - msg("Invalid format: %s: %s" % (addr,amt)) - sys.exit(3) - - if g.debug: - print "TX (cl): ", repr(tx_arg) - print "TX (proc): ", repr(tx) - - import decimal - try: - for i in tx.keys(): - tx[i] = trim_exponent(Decimal(tx[i])) - except decimal.InvalidOperation: - msg("Decimal conversion error in suboption '%s:%s'" % (i,tx[i])) - sys.exit(3) - - return tx + qmsg("Address mappings OK\n") -def check_addr_comment(label): +def check_addr_label(label): if len(label) > g.max_addr_label_len: msg("'%s': overlong label (length must be <=%s)" % (label,g.max_addr_label_len)) sys.exit(3) - for ch in list(label): + for ch in label: if ch not in g.addr_label_symbols: - msg("'%s': illegal character in label '%s'" % (ch,label)) - msg("Permitted characters: A-Za-z0-9, plus '%s'" % - "', '".join(g.addr_label_punc)) + msg(""" +"%s": illegal character in label "%s". +Only ASCII printable characters are permitted. +""".strip() % (ch,label)) sys.exit(3) def parse_addrs_file(f): + lines = get_lines_from_file(f,"address data",remove_comments=True) try: @@ -518,30 +619,23 @@ def parse_addrs_file(f): msg("'%s': invalid first line" % lines[0]) elif cbrace != '}': msg("'%s': invalid last line" % cbrace) - elif len(seed_id) != 8: + elif not is_mmgen_seed(seed_id): msg("'%s': invalid Seed ID" % seed_id) else: - try: - unhexlify(seed_id) - except: - msg("'%s': invalid Seed ID" % seed_id) - sys.exit(3) - ret = [] for i in lines[1:-1]: d = i.split(None,2) - try: d[0] = int(d[0]) - except: + if not is_mmgen_num(d[0]): msg("'%s': invalid address num. in line: %s" % (d[0],d)) sys.exit(3) - from mmgen.bitcoin import verify_addr - if not verify_addr(d[1]): - msg("'%s': invalid address" % d[1]) + if not is_btc_addr(d[1]): + msg("'%s': invalid Bitcoin address" % d[1]) sys.exit(3) - if len(d) == 3: check_addr_comment(d[2]) + if len(d) == 3: + check_addr_label(d[2]) ret.append(tuple(d)) @@ -553,52 +647,59 @@ def parse_addrs_file(f): def sign_transaction(c,tx_hex,sig_data,keys=None): if keys: - msg("%s keys total" % len(keys)) + qmsg("%s keys total" % len(keys)) if g.debug: print "Keys:\n %s" % "\n ".join(keys) + msg_r("Signing transaction...") from mmgen.rpc import exceptions - try: sig_tx = c.signrawtransaction(tx_hex,sig_data,keys) except exceptions.InvalidAddressOrKey: - msg("Invalid address or key") + msg("failed\nInvalid address or key") sys.exit(3) -# except: -# msg("Failed to sign transaction") -# sys.exit(3) return sig_tx -def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts): +def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False): - seed_ids = list(set([i['account'][:8] for i in mmgen_addrs])) - seed_ids_save = seed_ids[0:] - keys = [] + seed_ids = list(set([i[:8] for i in mmgen_addrs])) + seed_ids_save = seed_ids[0:] # deep copy + ret = [] + + seeds_keys = [i for i in seed_ids if i in seeds] while seed_ids: - infile = False - if infiles: - infile = infiles.pop() - 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) + if seeds_keys: + seed = seeds[seeds_keys.pop()] else: - b,p,v = ("A seed","","is") if len(seed_ids) == 1 else ("Seed","s","are") - msg("ERROR: %s source%s %s required for the following seed ID%s: %s" % - (b,p,v,p," ".join(seed_ids))) - sys.exit(2) + infile = False + if infiles: + infile = infiles.pop() + seed = get_seed_retry(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) + else: + b,p,v = ("A seed","","is") if len(seed_ids) == 1 \ + else ("Seed","s","are") + msg("ERROR: %s source%s %s required for the following seed ID%s: %s"% + (b,p,v,p," ".join(seed_ids))) + sys.exit(2) seed_id = make_chksum_8(seed) if seed_id in seed_ids: seed_ids.remove(seed_id) - seed_id_addrs = [ - int(i['account'].split()[0][9:]) for i in mmgen_addrs - if i['account'][:8] == seed_id] - - from mmgen.addr import generate_keys - keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)] + addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id] + seeds[seed_id] = seed + from mmgen.addr import generate_keys,generate_addrs + if gen_pairs: + o = {"gen_what":"addresses"} + ret += [("%s:%s" % (seed_id,i['num']),i['addr']) + for i in generate_addrs(seed, addr_ids, o)] + else: + ret += [i['wif'] for i in generate_keys(seed, addr_ids)] else: if seed_id in seed_ids_save: msg_r("Ignoring duplicate seed source") @@ -610,7 +711,7 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts): msg("Invalid input file: %s" % infile) sys.exit(2) - return keys + return ret def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts): @@ -642,15 +743,64 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts): return sig_tx +def preverify_keys(addrs_orig, keys_orig): + + addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[] + + if len(keys) < len(addrs): + msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" % + (len(keys),g.proj_name_cap,len(addrs))) + sys.exit(2) + + import mmgen.bitcoin as b + + qmsg_r('Checking that user-supplied key list contains valid keys...') + + invalid_keys = [] + + for n,k in enumerate(keys,1): + c = False if k[0] == '5' else True + if b.wiftohex(k,compressed=c) == False: + invalid_keys.append(k) + + if invalid_keys: + s = "" if len(invalid_keys) == 1 else "s" + msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s)) + sys.exit(2) + else: qmsg("OK") + + msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)') + + try: + for n,k in enumerate(keys,1): + msg_r("\rkey %s of %s" % (n,len(keys))) + c = False if k[0] == '5' else True + hexkey = b.wiftohex(k,compressed=c) + addr = b.privnum2addr(int(hexkey,16),compressed=c) + if addr in addrs: + addrs.remove(addr) + if not addrs: break + else: + wrong_keys.append(k) + except KeyboardInterrupt: + msg("\nSkipping") + else: + msg("") + if wrong_keys: + s = "" if len(wrong_keys) == 1 else "s" + msg("%s extra key%s found" % (len(wrong_keys),s)) + + if addrs: + s = "" if len(addrs) == 1 else "es" + msg("No keys found for the following non-%s address%s:" % + (g.proj_name_cap,s)) + print " %s" % "\n ".join(addrs) + sys.exit(2) + + 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]) - )) - -def get_addr_data(cmd_args): - for f in cmd_args: - data = parse_addrs_file(f) - print repr(data); sys.exit() # DEBUG +A key file must be supplied (or use the "-w" option) for the following +non-mmgen address%s: +""".strip() % ("" if len(other_addrs) == 1 else "es")) + print " %s" % "\n ".join([i['address'] for i in other_addrs]) diff --git a/mmgen/util.py b/mmgen/util.py index cce59503..aca04486 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -23,124 +23,21 @@ import sys import mmgen.config as g from binascii import hexlify,unhexlify from mmgen.bitcoin import b58decode_pad +from mmgen.term import * + +def msg(s): sys.stderr.write(s + "\n") +def msg_r(s): sys.stderr.write(s) +def qmsg(s): + if not g.quiet: sys.stderr.write(s + "\n") +def qmsg_r(s): + if not g.quiet: sys.stderr.write(s) +def vmsg(s): + if g.verbose: sys.stderr.write(s + "\n") +def vmsg_r(s): + if g.verbose: sys.stderr.write(s) -def msg(s): sys.stderr.write(s + "\n") -def msg_r(s): sys.stderr.write(s) def bail(): sys.exit(9) -def kb_hold_protect_unix(): - - fd = sys.stdin.fileno() - old = termios.tcgetattr(fd) - tty.setcbreak(fd) - - timeout = float(0.3) - - try: - while True: - key = select([sys.stdin], [], [], timeout)[0] - if key: sys.stdin.read(1) - else: break - except: - print "\nUser interrupt" - sys.exit(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) - - -def get_keypress_unix(prompt="",immed_chars=""): - - msg_r(prompt) - timeout = float(0.3) - - fd = sys.stdin.fileno() - old = termios.tcgetattr(fd) - tty.setcbreak(fd) - - try: - while True: - select([sys.stdin], [], [], False) - ch = sys.stdin.read(1) - 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 - second_key = select([sys.stdin], [], [], timeout)[0] - if second_key: continue - else: return ch - except: - print "\nUser interrupt" - sys.exit(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) - - -def kb_hold_protect_mswin(): - - timeout = float(0.5) - - try: - while True: - hit_time = time.time() - while True: - if msvcrt.kbhit(): - msvcrt.getch() - break - if float(time.time() - hit_time) > timeout: - return - except: - msg("\nUser interrupt") - sys.exit(1) - - -def get_keypress_mswin(prompt="",immed_chars=""): - - msg_r(prompt) - timeout = float(0.5) - - try: - while True: - if msvcrt.kbhit(): - ch = msvcrt.getch() - - if ord(ch) == 3: raise KeyboardInterrupt - - 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 - - hit_time = time.time() - - while True: - if msvcrt.kbhit(): break - if float(time.time() - hit_time) > timeout: - return ch - except: - msg("\nUser interrupt") - sys.exit(1) - - -try: - import tty, termios - from select import select - get_char = get_keypress_unix - kb_hold_protect = kb_hold_protect_unix -except: - try: - import msvcrt, time - get_char = get_keypress_mswin - kb_hold_protect = kb_hold_protect_mswin - except: - if not sys.platform.startswith("linux") \ - and not sys.platform.startswith("win"): - msg("Unsupported platform: %s" % sys.platform) - msg("This program currently runs only on Linux and Windows") - else: - msg("Unable to set terminal mode") - sys.exit(2) - - def my_raw_input(prompt,echo=True,allowed_chars=""): try: if echo: @@ -148,8 +45,8 @@ def my_raw_input(prompt,echo=True,allowed_chars=""): else: from getpass import getpass reply = getpass(prompt) - except: - print "\nUser interrupt" + except KeyboardInterrupt: + msg("\nUser interrupt") sys.exit(1) kb_hold_protect() @@ -202,7 +99,7 @@ cmessages = { 'unencrypted_secret_keys': """ This program generates secret keys from your {} seed, outputting them in UNENCRYPTED form. Generate only the key(s) you need and guard them carefully. -""".format(g.proj_name), +""".format(g.proj_name_cap), 'brain_warning': """ ############################## EXPERTS ONLY! ############################## @@ -223,7 +120,7 @@ future, you must continue using these same parameters def confirm_or_exit(message, question, expect="YES"): - msg("") + vmsg("") m = message.strip() if m: msg(m) @@ -353,7 +250,7 @@ def get_new_passphrase(what, opts): pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts)) if g.debug: print "Passphrases: [%s] [%s]" % (pw,pw2) if pw == pw2: - msg("%ss match" % what.capitalize()) + vmsg("%ss match" % what.capitalize()) break else: msg("%ss do not match" % what.capitalize()) @@ -362,7 +259,7 @@ def get_new_passphrase(what, opts): g.passwd_max_tries) sys.exit(2) - if pw == "": msg("WARNING: Empty passphrase") + if pw == "": qmsg("WARNING: Empty passphrase") return pw @@ -384,17 +281,17 @@ def _get_seed_from_brain_passphrase(words,opts): if g.debug: print "Sanitized brain passphrase: %s" % bp seed_len,hash_preset = _get_from_brain_opt_params(opts) if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset) - msg_r("Hashing brainwallet data. Please wait...") + vmsg_r("Hashing brainwallet data. Please wait...") # Use buflen arg of scrypt.hash() to get seed of desired length seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8) - msg("Done") + vmsg("Done") return seed -def encrypt_seed(seed, key, opts): +def encrypt_seed(seed, key): """ - Encrypt a seed for a {} deterministic wallet - """.format(g.proj_name) + Encrypt a seed for an {} deterministic wallet + """.format(g.proj_name_cap) # 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR from Crypto.Cipher import AES @@ -403,14 +300,14 @@ def encrypt_seed(seed, key, opts): c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128)) enc_seed = c.encrypt(seed) - msg_r("Performing a test decryption of the seed...") + vmsg_r("Performing a test decryption of the seed...") c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128)) dec_seed = c.decrypt(enc_seed) - if dec_seed == seed: msg("done") + if dec_seed == seed: vmsg("done") else: - msg("FAILED.\nDecrypted seed doesn't match original seed") + msg("ERROR.\nDecrypted seed doesn't match original seed") sys.exit(2) return enc_seed @@ -576,11 +473,11 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts): chk = make_chksum_6(" ".join(lines)) - confirm = False if 'quiet' in opts else True + confirm = False if g.quiet else True write_to_file(outfile, "\n".join((chk,)+lines)+"\n", confirm) msg("Wallet saved to file '%s'" % outfile) - if 'verbose' in opts: + if g.verbose: _display_control_data(label,metadata,hash_preset,salt,enc_seed) @@ -592,10 +489,10 @@ def write_walletdat_dump_to_file(wallet_id,data,num_keys,ext,what,opts): msg("wallet.dat %s saved to file '%s'" % (what,outfile)) -def compare_checksums(chksum1, desc1, chksum2, desc2): +def _compare_checksums(chksum1, desc1, chksum2, desc2): if chksum1.lower() == chksum2.lower(): - msg("OK (%s)" % chksum1.upper()) + vmsg("OK (%s)" % chksum1.upper()) return True else: if g.debug: @@ -608,6 +505,8 @@ def _is_hex(s): except: return False else: return True +def match_ext(addr,ext): + return addr.split(".")[-1] == ext def _check_mmseed_format(words): @@ -629,10 +528,7 @@ def _check_mmseed_format(words): return valid -def check_wallet_format(infile, lines, opts): - - def vmsg(s): - if 'verbose' in opts: msg(s) +def _check_wallet_format(infile, lines): what = "wallet file '%s'" % infile valid = False @@ -660,17 +556,19 @@ def _check_chksum_6(chk,val,desc,infile): msg("%s checksum passed: %s" % (desc.capitalize(),chk)) -def get_data_from_wallet(infile,opts,silent=False): +def get_data_from_wallet(infile,silent=False): + # Don't make this a qmsg: User will be prompted for passphrase and must see + # the filename. if not silent: - msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile)) + msg("Getting {} wallet data from file '{}'".format(g.proj_name_cap,infile)) f = open_file_or_exit(infile, 'r') lines = [i.strip() for i in f.readlines()] f.close() - check_wallet_format(infile, lines, opts) + _check_wallet_format(infile, lines) label = lines[1] @@ -711,7 +609,7 @@ def _get_words_from_user(prompt, opts): def _get_words_from_file(infile,what): - msg("Getting %s from file '%s'" % (what,infile)) + qmsg("Getting %s from file '%s'" % (what,infile)) f = open_file_or_exit(infile, 'r') # split() also strips words = f.read().split() @@ -721,7 +619,8 @@ def _get_words_from_file(infile,what): def get_lines_from_file(infile,what="",remove_comments=False): - if what != "": msg("Getting %s from file '%s'" % (what,infile)) + if what != "": + qmsg("Getting %s from file '%s'" % (what,infile)) f = open_file_or_exit(infile,'r') lines = f.read().splitlines(); f.close() if remove_comments: @@ -738,7 +637,7 @@ def get_lines_from_file(infile,what="",remove_comments=False): def get_data_from_file(infile,what="data"): - msg("Getting %s from file '%s'" % (what,infile)) + qmsg("Getting %s from file '%s'" % (what,infile)) f = open_file_or_exit(infile,'r') data = f.read() f.close() @@ -755,18 +654,18 @@ def _get_seed_from_seed_data(words): seed_b58 = "".join(words[1:]) chk = make_chksum_6(seed_b58) - msg_r("Validating %s checksum..." % g.seed_ext) + vmsg_r("Validating %s checksum..." % g.seed_ext) - if compare_checksums(chk, "from seed", stored_chk, "from input"): + if _compare_checksums(chk, "from seed", stored_chk, "from input"): seed = b58decode_pad(seed_b58) if seed == False: msg("Invalid b58 number: %s" % val) return False - msg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed))) + vmsg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed))) return seed else: - msg("Invalid checksum for {} seed".format(g.proj_name)) + msg("Invalid checksum for {} seed".format(g.proj_name_cap)) return False @@ -791,7 +690,8 @@ def get_mmgen_passphrase(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") + return get_data_from_file(opts['passwd_file'], + "passphrase").strip("\r\n") else: return my_raw_input(prompt, echo=True if 'echo_passphrase' in opts else False) @@ -800,14 +700,14 @@ def get_bitcoind_passphrase(prompt,opts): def get_seed_from_wallet( infile, opts, - prompt="Enter {} wallet passphrase: ".format(g.proj_name), + prompt="Enter {} wallet passphrase: ".format(g.proj_name_cap), silent=False ): - wdata = get_data_from_wallet(infile,opts,silent=silent) + wdata = get_data_from_wallet(infile,silent=silent) label,metadata,hash_preset,salt,enc_seed = wdata - if 'verbose' in opts: _display_control_data(*wdata) + if g.verbose: _display_control_data(*wdata) passwd = get_mmgen_passphrase(prompt,opts) @@ -818,21 +718,21 @@ def get_seed_from_wallet( def make_key(passwd, salt, hash_preset): - msg_r("Hashing passphrase. Please wait...") + vmsg_r("Hashing passphrase. Please wait...") key = _scrypt_hash_passphrase(passwd, salt, hash_preset) - msg("done") + vmsg("done") return key def decrypt_seed(enc_seed, key, seed_id, key_id): - msg_r("Checking key...") + vmsg_r("Checking key...") chk = make_chksum_8(key) - if not compare_checksums(chk, "of key", key_id, "in header"): + if not _compare_checksums(chk, "of key", key_id, "in header"): msg("Incorrect passphrase") return False - msg_r("Decrypting seed with key...") + vmsg_r("Decrypting seed with key...") from Crypto.Cipher import AES from Crypto.Util import Counter @@ -841,13 +741,13 @@ def decrypt_seed(enc_seed, key, seed_id, key_id): dec_seed = c.decrypt(enc_seed) chk = make_chksum_8(dec_seed) - if compare_checksums(chk,"of decrypted seed",seed_id,"in header"): - msg("Passphrase is OK") + if _compare_checksums(chk,"of decrypted seed",seed_id,"in header"): + qmsg("Passphrase is OK") else: if not g.debug: msg_r("Checking key ID...") chk = make_chksum_8(key) - if compare_checksums(chk, "of key", key_id, "in header"): + if _compare_checksums(chk, "of key", key_id, "in header"): msg("Key ID is correct but decryption of seed failed") else: msg("Incorrect passphrase") @@ -894,10 +794,10 @@ def get_seed(infile,opts,silent=False): if 'from_brain' not in opts: msg("'--from-brain' parameters must be specified for brainwallet file") sys.exit(2) - if 'quiet' not in opts: + if not g.quiet: confirm_or_exit( cmessages['brain_warning'].format( - g.proj_name.capitalize(), *_get_from_brain_opt_params(opts)), + g.proj_name_cap, *_get_from_brain_opt_params(opts)), "continue") prompt = "Enter brainwallet passphrase: " words = _get_words(infile,"brainwallet data",prompt,opts) @@ -954,10 +854,10 @@ def do_pager(text): else: try: p.communicate(text+end+"\n") - except: + except KeyboardInterrupt: # Has no effect. Why? if pager != "less": - msg("\n(Interrupted by user)\n") + msg("\n(User interrupt)\n") finally: msg_r("\r") break diff --git a/mmgen/walletgen.py b/mmgen/walletgen.py index 7d6dd91a..adb1153b 100755 --- a/mmgen/walletgen.py +++ b/mmgen/walletgen.py @@ -20,14 +20,15 @@ walletgen.py: Routines used for seed generation and wallet creation """ import sys -from mmgen.util import msg, msg_r, get_char, prompt_and_get_char +import mmgen.config as g +from mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char from binascii import hexlify def get_random_data_from_user(opts): ulen = opts['usr_randlen'] - if 'quiet' in opts: + if g.quiet: msg("Enter %s random symbols" % ulen) else: msg(""" @@ -54,7 +55,7 @@ displayed on the screen. intervals.append(now - saved_time) saved_time = now - if 'quiet' in opts: + if g.quiet: msg_r("\r") else: msg_r("\rThank you. That's enough." + " "*15 + "\n\n") diff --git a/setup.py b/setup.py index d4f09371..71f6b0b4 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup setup( name = 'mmgen', - version = '0.6.9', + version = '0.7.0', author = 'Philemon', author_email = 'mmgen-py@yandex.com', url = 'https://github.com/mmgen/mmgen', @@ -19,6 +19,7 @@ setup( 'mmgen.mnemonic', 'mmgen.mn_tirosh', 'mmgen.Opts', + 'mmgen.term', 'mmgen.tx', 'mmgen.util', 'mmgen.walletgen',