From e18c75eb364ee092ab8bb18bc4ca8047bdf8873a Mon Sep 17 00:00:00 2001 From: philemon Date: Sun, 2 Feb 2014 22:53:47 +0400 Subject: [PATCH] renamed: scripts/pywallet.py -> mmgen-pywallet --- scripts/pywallet.py => mmgen-pywallet | 263 +++++++++++--------------- mmgen/utils.py | 26 ++- mmgen/walletgen.py | 2 +- 3 files changed, 131 insertions(+), 160 deletions(-) rename scripts/pywallet.py => mmgen-pywallet (91%) diff --git a/scripts/pywallet.py b/mmgen-pywallet similarity index 91% rename from scripts/pywallet.py rename to mmgen-pywallet index dfb2c539..27890e4b 100755 --- a/scripts/pywallet.py +++ b/mmgen-pywallet @@ -1,4 +1,23 @@ #!/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 . +""" +mmgen-pywallet: Dump contents of a bitcoind wallet to file +""" # Changes by Philemon: # password entry at prompt @@ -24,6 +43,7 @@ # Alex Martelli (http://www.aleax.it) # Ported from C code written by Laurent Haan (http://www.progressive-coding.com) +from mmgen.Opts import * from mmgen.utils import msg from bsddb.db import * import os, sys, time @@ -46,15 +66,42 @@ json_db = {} private_keys = [] password = None -# def determine_db_dir(): -# import os -# import os.path -# import platform -# if platform.system() == "Darwin": -# return os.path.expanduser("~/Library/Application Support/Bitcoin/") -# elif platform.system() == "Windows": -# return os.path.join(os.environ['APPDATA'], "Bitcoin") -# return os.path.expanduser("~/.bitcoin") +prog_name = sys.argv[0].split("/")[-1] + +help_data = { + 'prog_name': prog_name, + 'desc': "Dump contents of a bitcoind wallet to file", + 'usage': "[opts] ", + 'options': """ +-h, --help Print this help message +-d, --outdir d Specify an alternate directory 'd' for output +-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 +-S, --stdout Dump to stdout rather than file +""" +} + +short_opts = "hd:jkaK:qS" +long_opts = "help","outdir=","json","keys","addrs","keysforaddrs=","quiet","stdout" + +opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts) + +from mmgen.utils import check_infile,check_opts + +if len(cmd_args) == 1: + check_infile(cmd_args[0]) +else: + usage(help_data) + +check_opts(opts,('outdir',)) + +if ('json' not in opts and 'keys' not in opts + and 'addrs' not in opts and 'keysforaddrs' not in opts): + usage(help_data) + # from the SlowAES project, http://code.google.com/p/slowaes (aes.py) @@ -1465,7 +1512,6 @@ def update_wallet(db, type, data): # wallet.dat reader / writer def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transactions, transaction_filter): - global password db = open_wallet(db_env, db_file) @@ -1475,6 +1521,8 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction def item_callback(type, d): + global password + if type == "name": json_db['names'][d['hash']] = d['name'] @@ -1522,7 +1570,12 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction mkey['vchOtherDerivationParameters'] = d['vchOtherDerivationParameters'].encode('hex') json_db['mkey'] = mkey - if password: + 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: ") + + if password != None: global crypter if crypter == 'pycrypto': crypter = Crypter_pycrypto() @@ -1543,7 +1596,7 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction elif type == "acc": json_db['acc'] = d['account'] - msg("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key']))) +# msg("Account %s (current key: %s)"%(d['account'], public_key_to_bc_address(d['public_key']))) elif type == "acentry": json_db['acentry'] = (d['account'], d['nCreditDebit'], d['otherAccount'], time.ctime(d['nTime']), d['n'], d['comment']) @@ -1565,14 +1618,7 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction else: k["reserve"] = 1 - crypted = 'mkey' in json_db.keys() - - if (crypted and not password) and \ - (options.dump or options.keysforaddrs or options.privkeys): - print("Encrypted wallet. Specify '--password' to decrypt") - sys.exit(1) - - if crypted and password: + if 'mkey' in json_db.keys() and password != None: check = True for k in json_db['keys']: ckey = k['ckey'].decode('hex') @@ -1599,157 +1645,74 @@ def read_wallet(json_db, db_env, db_file, print_wallet, print_wallet_transaction del(json_db['pool']) del(json_db['names']) -def importprivkey(db, sec): - - pkey = regenerate_key(sec) - if not pkey: - return False - - compressed = is_compressed(sec) - - secret = GetSecret(pkey) - private_key = GetPrivKey(pkey, compressed) - public_key = GetPubKey(pkey, compressed) - addr = public_key_to_bc_address(public_key) - - print "Address: %s" % addr - print "Privkey: %s" % SecretToASecret(secret, compressed) - - global crypter, password, json_db - - crypted = 'mkey' in json_db.keys() - - if crypted: - if password: - crypter.SetIV(Hash(public_key)) - crypted_key = crypter.Encrypt(secret) - update_wallet(db, 'ckey', { 'public_key' : public_key, 'crypted_key' : crypted_key }) - update_wallet(db, 'name', { 'hash' : addr, 'name' : '' }) - return True - else: - logging.error("password not specified") - sys.exit(1) - else: - update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key }) - update_wallet(db, 'name', { 'hash' : addr, 'name' : '' }) - return True - - return False - def parse_wallet_file_arg(path): - import os - try: - os.stat(path) - except: - print "File '%s' does not exist" % path - sys.exit(1) - s = path.rfind("/") if path[0] == '/': - if s == 0: - return "/", path - else: - return path[0:s], path[s+1:] + 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:] + if s == -1: return curdir,path + else: return curdir + "/" + path[0:s], path[s+1:] +# main() -from optparse import OptionParser -def main(): - - global max_version, addrtype - - parser = OptionParser(usage="%prog [options] walletfile", version="%prog 1.2") - - parser.add_option("--dumpwallet", dest="dump", action="store_true", - help="dump wallet in json format") - - parser.add_option("--privkeys", dest="privkeys", action="store_true", - help="dump all private keys in wallet (flat list)") - - parser.add_option("--addrs", dest="addrs", action="store_true", - help="dump all addresses in wallet (flat list)") - - parser.add_option("--keysforaddrs", dest="keysforaddrs", - help="dump all private keys corresponding to addresses in file KEYSFORADDRS (flat list)") - - parser.add_option("--importprivkey", dest="key", - help="import private key from vanitygen") - - parser.add_option("--testnet", dest="testnet", action="store_true", - help="use testnet subdirectory and address type") - - parser.add_option("--password", dest="password", action="store_true", - help="prompt for password for the encrypted wallet") - - global options - - (options, args) = parser.parse_args() - - if options.dump is None and options.key is None \ - and options.keysforaddrs is None \ - and options.addrs is None and options.privkeys is None: - print "A mandatory option is missing\n" - parser.print_help() - sys.exit(1) - - db_dir,db_file = parse_wallet_file_arg(args[0]) +db_dir,db_file = parse_wallet_file_arg(cmd_args[0]) # print "[%s] [%s]" % (db_dir,db_file) -# sys.exit() - if options.testnet: addrtype = 111 +db_env = create_env(db_dir) - db_env = create_env(db_dir) +read_wallet(json_db, db_env, db_file, True, True, "") - global password +if json_db.get('minversion') > max_version: + print "Version mismatch (must be <= %d)" % max_version + exit(1) - if options.password: - from mmgen.utils import my_getpass - password = my_getpass("Enter password: ") +wallet_addrs = [i['addr'] for i in json_db['keys']] - read_wallet(json_db, db_env, db_file, True, True, "") +data,ext,what = [],"","" - if json_db.get('minversion') > max_version: - print "Version mismatch (must be <= %d)" % max_version - exit(1) +if 'json' in opts: + data = [ json.dumps(json_db, sort_keys=True, indent=4) ] + ext,what = "json","json dump" - if options.dump: - print json.dumps(json_db, sort_keys=True, indent=4) +elif 'keys' in opts: + for i in json_db['keys']: data.append(i['sec']) + ext,what = "keys","private keys" - elif options.privkeys: - for i in json_db['keys']: - print i['sec'] +elif 'addrs' in opts: + for i in json_db['keys']: data.append(i['addr']) + ext,what = "addrs","addresses" - elif options.addrs: - for i in json_db['keys']: - print i['addr'] +elif 'keysforaddrs' in opts: + from mmgen.utils 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) + ext,what = "keys","private keys" - elif options.keysforaddrs: - from mmgen.utils import get_lines_from_file - addrs = get_lines_from_file(options.keysforaddrs,"addresses") - for i in json_db['keys']: - if i['addr'] in addrs: - print i['sec'] +len_arg = "%s" % len(wallet_addrs) \ + if len(data) == len(wallet_addrs) or ext == "json" \ + else "%s:%s" % (len(data),len(wallet_addrs)) - elif options.key: - if options.key in private_keys: - print "Already exists" - else: - db = open_wallet(db_env, db_file, writable=True) +from mmgen.utils import make_chksum_8,write_walletdat_dump_to_file,write_to_stdout +wallet_id = make_chksum_8(str(sorted(wallet_addrs))) - if importprivkey(db, options.key): - print "Imported successfully" - else: - print "Bad private key" +data = "\n".join(data) + "\n" - db.close() - -if __name__ == '__main__': - main() +# Output data +if 'stdout' in opts: + if 'addrs' in opts or 'quiet' in opts: confirm = False + else: confirm = True + write_to_stdout(data,"secret keys",confirm) +elif not sys.stdout.isatty(): + write_to_stdout(data,"secret keys",confirm=False) +else: + write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts) diff --git a/mmgen/utils.py b/mmgen/utils.py index 54c4f170..abd1ec2b 100755 --- a/mmgen/utils.py +++ b/mmgen/utils.py @@ -103,15 +103,15 @@ def check_opts(opts,keys): try: mode = os.stat(d).st_mode except: - msg("Unable to stat requested %s '%s'. Aborting" % (what,d)) + msg("Unable to stat requested %s '%s'" % (what,d)) sys.exit(1) if not stat.S_ISDIR(mode): - msg("Requested %s '%s' is not a directory. Aborting" %(what,d)) + msg("Requested %s '%s' is not a directory" % (what,d)) sys.exit(1) if not os.access(d, os.W_OK|os.X_OK): - msg("Requested %s '%s' is unwritable by you. Aborting"%(what,d)) + msg("Requested %s '%s' is unwritable by you" % (what,d)) sys.exit(1) elif key == 'label': @@ -266,15 +266,15 @@ def check_infile(f): try: mode = os.stat(f).st_mode except: - msg("Unable to stat requested input file '%s'. Aborting" % f) + msg("Unable to stat requested input file '%s'" % f) sys.exit(1) if not stat.S_ISREG(mode) or stat.S_ISLNK(mode): - msg("Requested input file '%s' is not a file. Aborting" % f) + msg("Requested input file '%s' is not a file" % f) sys.exit(1) if not os.access(f, os.R_OK): - msg("Requested input file '%s' is unreadable by you. Aborting" % f) + msg("Requested input file '%s' is unreadable by you" % f) sys.exit(1) @@ -394,13 +394,13 @@ def encrypt_seed(seed, key, opts): if dec_seed == seed: msg("done") else: - msg("FAILED.\nDecrypted seed doesn't match original seed. Aborting.") + msg("FAILED.\nDecrypted seed doesn't match original seed") sys.exit(2) return enc_seed -def write_to_stdout(data, what, confirm=True): +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(): @@ -423,7 +423,7 @@ def open_file_or_exit(filename,mode): f = open(filename, mode) except: what = "reading" if mode == 'r' else "writing" - msg("Unable to open file '%s' for %s" % (infile,what)) + msg("Unable to open file '%s' for %s" % (filename,what)) sys.exit(2) return f @@ -561,6 +561,14 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts): _display_control_data(label,metadata,hash_preset,salt,enc_seed) +def write_walletdat_dump_to_file(wallet_id,data,num_keys,ext,what,opts): + outfile = "wd_{}[{}].{}".format(wallet_id,num_keys,ext) + if 'outdir' in opts: + outfile = "%s/%s" % (opts['outdir'], outfile) + write_to_file(outfile,data,confirm=False) + msg("wallet.dat %s saved to file '%s'" % (what,outfile)) + + def compare_checksums(chksum1, desc1, chksum2, desc2): if chksum1.lower() == chksum2.lower(): diff --git a/mmgen/walletgen.py b/mmgen/walletgen.py index a49d4f65..1640f895 100755 --- a/mmgen/walletgen.py +++ b/mmgen/walletgen.py @@ -67,7 +67,7 @@ displayed on the screen. "User random data successfully acquired. Press ENTER to continue: ") raw_input() except: - msg("\nUser random input interrupted. Aborting") + msg("\nUser random input interrupted") sys.exit(1) finally: os.system("stty sane")