From 2b183c18f3c800daaabcbff45b31b2857a7b0b17 Mon Sep 17 00:00:00 2001 From: philemon Date: Tue, 5 Aug 2014 22:24:27 +0400 Subject: [PATCH] New script launcher, better KeyboardInterrupt handling, bugfixes in term.py --- mmgen-addrgen | 184 +---- mmgen-addrimport | 126 +-- mmgen-passchg | 113 +-- mmgen-pywallet | 1662 +------------------------------------ mmgen-tool | 58 +- mmgen-txcreate | 208 +---- mmgen-txsend | 72 +- mmgen-txsign | 198 +---- mmgen-walletchk | 103 +-- mmgen-walletgen | 136 +--- mmgen/Opts.py | 9 +- mmgen/__init__.py | 6 +- mmgen/addr.py | 49 +- mmgen/bitcoin.py | 3 +- mmgen/config.py | 5 +- mmgen/crypto.py | 16 +- mmgen/license.py | 4 +- mmgen/main.py | 44 + mmgen/main_addrgen.py | 191 +++++ mmgen/main_addrimport.py | 140 ++++ mmgen/main_passchg.py | 127 +++ mmgen/main_pywallet.py | 1677 ++++++++++++++++++++++++++++++++++++++ mmgen/main_tool.py | 70 ++ mmgen/main_txcreate.py | 221 +++++ mmgen/main_txsend.py | 85 ++ mmgen/main_txsign.py | 213 +++++ mmgen/main_walletchk.py | 115 +++ mmgen/main_walletgen.py | 149 ++++ mmgen/mn_electrum.py | 2 +- mmgen/mn_tirosh.py | 2 +- mmgen/mnemonic.py | 5 +- mmgen/term.py | 116 ++- mmgen/tests/bitcoin.py | 42 +- mmgen/tool.py | 13 +- mmgen/tx.py | 12 +- mmgen/util.py | 46 +- 36 files changed, 3247 insertions(+), 2975 deletions(-) create mode 100755 mmgen/main.py create mode 100755 mmgen/main_addrgen.py create mode 100755 mmgen/main_addrimport.py create mode 100755 mmgen/main_passchg.py create mode 100755 mmgen/main_pywallet.py create mode 100755 mmgen/main_tool.py create mode 100755 mmgen/main_txcreate.py create mode 100755 mmgen/main_txsend.py create mode 100755 mmgen/main_txsign.py create mode 100755 mmgen/main_walletchk.py create mode 100755 mmgen/main_walletgen.py diff --git a/mmgen-addrgen b/mmgen-addrgen index e46a1863..4bfdef5e 100755 --- a/mmgen-addrgen +++ b/mmgen-addrgen @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -17,183 +17,9 @@ # along with this program. If not, see . """ -mmgen-addrgen: Generate a list or range of addresses from a mmgen - deterministic wallet. - Call as 'btc-keygen' to allow key generation. +mmgen-addrgen: Generate a series or range of addresses from an MMGen + deterministic wallet """ -import sys - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.license import * -from mmgen.util import * -from mmgen.crypto import * -from mmgen.addr import * -from mmgen.tx import make_addr_data_chksum - -what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses" - -help_data = { - 'prog_name': g.prog_name, - 'desc': """Generate a list or range of {} from an {g.proj_name} wallet, - mnemonic, seed or password""".format(what,g=g), - 'usage':"[opts] [infile]
", - 'options': """ --h, --help Print this help message{} --d, --outdir= d Specify an alternate directory 'd' for output --c, --save-checksum Save address list checksum to file --e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry{} --H, --show-hash-presets Show information on available hash presets --K, --no-keyconv Use internal libraries for address generation - instead of 'keyconv' --l, --seed-len= N Length of seed. Options: {seed_lens} - (default: {g.seed_len}) --p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when - hashing password (default: '{g.hash_preset}') --P, --passwd-file= f Get passphrase from file 'f' --q, --quiet Suppress warnings; overwrite files without - prompting --S, --stdout Print {what} to stdout --v, --verbose Produce more verbose output{} - --b, --from-brain= l,p Generate {what} from a user-created password, - i.e. a "brainwallet", using seed length 'l' and - hash preset 'p' (comma-separated) --g, --from-incog Generate {what} from an incognito wallet --X, --from-incog-hex Generate {what} from incognito hexadecimal wallet --G, --from-incog-hidden=f,o,l Generate {what} from incognito data in file - 'f' at offset 'o', with seed length of 'l' --m, --from-mnemonic Generate {what} from an electrum-like mnemonic --s, --from-seed Generate {what} from a seed in .{g.seed_ext} format -""".format( - *( - ( -"\n-A, --no-addresses Print only secret keys, no addresses", -"\n-f, --flat-list Produce a flat list of keys suitable for use with" + -"\n '{}-txsign'".format(g.proj_name.lower()), -"\n-x, --b16 Print secret keys in hexadecimal too" - ) - if what == "keys" else ("","","")), - seed_lens=", ".join([str(i) for i in g.seed_lens]), - what=what, g=g -), - 'notes': """ - -Addresses are given in a comma-separated list. Hyphen-separated ranges are -also allowed.{} - -If available, the external 'keyconv' program will be used for address -generation. - -Data for the --from- options will be taken from if -is specified. Otherwise, the user will be prompted to enter the data. - -For passphrases all combinations of whitespace are equal, and leading and -trailing space are ignored. This permits reading passphrase data from a -multi-line file with free spacing and indentation. This is particularly -convenient for long brainwallet passphrases, for example. - -BRAINWALLET NOTE: - -As brainwallets require especially strong hashing to thwart dictionary -attacks, the brainwallet hash preset must be specified by the user, using -the 'p' parameter of the '--from-brain' option - -The '--from-brain' option also requires the user to specify a seed length -(the 'l' parameter) - -For a brainwallet passphrase to always generate the same keys and addresses, -the same 'l' and 'p' parameters to '--from-brain' must be used in all future -invocations with that passphrase -""".format("\n\nBy default, both addresses and secret keys are generated." - if what == "keys" else "") -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'show_hash_presets' in opts: show_hash_presets() -if 'verbose' in opts: g.verbose = True -if 'quiet' in opts: g.quiet = True -if 'from_incog_hex' in opts or 'from_incog_hidden' in opts: - opts['from_incog'] = True - -if g.debug: show_opts_and_cmd_args(opts,cmd_args) - -if len(cmd_args) == 1 and ( - 'from_mnemonic' in opts - or 'from_brain' in opts - or 'from_seed' in opts - or 'from_incog_hidden' in opts - ): - infile,addr_idx_arg = "",cmd_args[0] -elif len(cmd_args) == 2: - infile,addr_idx_arg = cmd_args - check_infile(infile) -else: usage(help_data) - -addr_idxs = parse_address_list(addr_idx_arg) - -if not addr_idxs: sys.exit(2) - -do_license_msg() - -# Interact with user: -if what == "keys" and not g.quiet: - confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue') - -# Generate data: - -seed = get_seed_retry(infile,opts) -seed_id = make_chksum_8(seed) - -for l in ( - ('flat_list', 'no_addresses'), - ('flat_list', 'b16'), -): warn_incompatible_opts(opts,l) - -opts['gen_what'] = \ - ("addrs") if what == "addresses" else ( - ("keys") if 'no_addresses' in opts else ("addrs","keys")) - -addr_data = generate_addrs(seed, addr_idxs, opts) -addr_data_chksum = make_addr_data_chksum([(a.num,a.addr) - for a in addr_data]) if 'addrs' in opts['gen_what'] else "" -addr_data_str = format_addr_data( - addr_data, addr_data_chksum, seed_id, addr_idxs, opts) - -outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs)) - -if 'flat_list' in opts: - confirm = False if g.quiet else True - outfile = "%s.%s" % (outfile_base,g.keylist_ext) - if (user_confirm("Encrypt key list?")): - enc_data = mmgen_encrypt(addr_data_str,"",opts) - outfile += "."+g.mmenc_ext - write_to_file(outfile,enc_data,opts,"encrypted key list",confirm,True) - else: - write_to_file(outfile,addr_data_str,opts,"key list",confirm,True) - sys.exit() - -# Output data: -if 'stdout' in opts: - confirm = True if (what == "keys" and not g.quiet) else False - write_to_stdout(addr_data_str,what,confirm) -elif not sys.stdout.isatty(): - write_to_stdout(addr_data_str,what,confirm=False) -else: - confirm = False if g.quiet else True - outfile = outfile_base + "." + ( - g.keylist_ext if 'flat_list' in opts else ( - g.keyfile_ext if opts['gen_what'] == ("keys") else ( - g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys"))) - write_to_file(outfile,addr_data_str,opts,what,confirm,True) - -if 'addrs' in opts['gen_what']: - msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum)) - if 'save_checksum' in opts: - a = "address data checksum" - write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,confirm,True) - else: - qmsg("This checksum will be used to verify the address file in the future.") - qmsg("Record it to a safe location.") +import mmgen.main +mmgen.main.main("addrgen") diff --git a/mmgen-addrimport b/mmgen-addrimport index d827789c..33e43df9 100755 --- a/mmgen-addrimport +++ b/mmgen-addrimport @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -17,126 +17,8 @@ # along with this program. If not, see . """ -mmgen-addrimport: Import addresses into a bitcoind watching wallet. +mmgen-addrimport: Import addresses into a MMGen bitcoind watching wallet """ -import sys -from mmgen.Opts import * -from mmgen.license import * -from mmgen.util import * -from mmgen.tx import connect_to_bitcoind,parse_addrs_file - -help_data = { - 'prog_name': g.prog_name, - 'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind - watching wallet""".format(pnm=g.proj_name), - 'usage':"[opts] [mmgen address file]", - 'options': """ --h, --help Print this help message --l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f' --q, --quiet Suppress warnings --r, --rescan Rescan the blockchain. Required if address to import is - on the blockchain and has a balance. Rescanning is slow. -""" -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True - -if len(cmd_args) != 1 and not 'addrlist' in opts: - msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)") - sys.exit(1) - -if cmd_args: - check_infile(cmd_args[0]) - seed_id,addr_data = parse_addrs_file(cmd_args[0]) -else: - seed_id,addr_data = "",[] - -if 'addrlist' in opts: - lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses", - trim_comments=True) - addr_data += [(None,l) for l in lines] - -from mmgen.bitcoin import verify_addr -qmsg_r("Validating addresses...") -for i in addr_data: - if not verify_addr(i[1],verbose=True): - msg("%s: invalid address" % i) - sys.exit(2) -qmsg("OK") - -import mmgen.config as g -g.http_timeout = 3600 - -c = connect_to_bitcoind() - -m = """ -WARNING: You've chosen the '--rescan' option. Rescanning the block chain is -necessary only if an address you're importing is already on the block chain -and has a balance. Note that the rescanning process is very slow (>30 min. -for each imported address on a low-powered computer). - """.strip() if "rescan" in opts else """ -WARNING: If any of the addresses you're importing is already on the block chain -and has a balance, you must exit the program now and rerun it using the -'--rescan' option. Otherwise you may ignore this message and continue. -""".strip() - -if g.quiet: m = "" -confirm_or_exit(m, "continue", expect="YES") - -err_flag = False - -def import_address(addr,label,rescan): - try: - c.importaddress(addr,label,rescan) - except: - global err_flag - err_flag = True - - -w1 = len(str(len(addr_data))) * 2 + 2 -w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12 - -if "rescan" in opts: - import threading - import time - msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s" -else: - msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s" - -msg("Importing addresses") -for n,i in enumerate(addr_data): - if i[0]: - comment = " " + i[2] if len(i) == 3 else "" - label = "%s:%s%s" % (seed_id,i[0],comment) - else: label = "non-mmgen" - - if "rescan" in opts: - t = threading.Thread(target=import_address, args=(i[1],label,True)) - t.daemon = True - t.start() - - start = int(time.time()) - - while True: - if t.is_alive(): - elapsed = int(time.time() - start) - msg_r(msg_fmt % ( - secs_to_hms(elapsed), - ("%s/%s:" % (n+1,len(addr_data))), - i[1], "(" + label + ")" - ) - ) - time.sleep(1) - else: - if err_flag: msg("\nImport failed"); sys.exit(2) - msg("\nOK") - break - else: - import_address(i[1],label,rescan=False) - msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))), - i[1], "(" + label + ")")) - if err_flag: msg("\nImport failed"); sys.exit(2) - msg(" - OK") +import mmgen.main +mmgen.main.main("addrimport") diff --git a/mmgen-passchg b/mmgen-passchg index ae3858a5..f5ea5a29 100755 --- a/mmgen-passchg +++ b/mmgen-passchg @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,114 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or +mmgen-passchg: Change an MMGen deterministic wallet's passphrase, label or hash preset """ -import sys -from mmgen.Opts import * -from mmgen.util import * -from mmgen.crypto import * -import mmgen.config as g - -help_data = { - 'prog_name': g.prog_name, - 'desc': """Change the passphrase, hash preset or label of an {} - deterministic wallet""".format(g.proj_name), - 'usage': "[opts] [filename]", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --H, --show-hash-presets Show information on available hash presets --k, --keep-old-passphrase Keep old passphrase (use when changing hash - strength or label only) --L, --label= l Change the wallet's label to 'l' --p, --hash-preset= p Change scrypt.hash() parameters to preset 'p' - (default: '{g.hash_preset}') --P, --passwd-file= f Get new passphrase from file 'f' --r, --usr-randchars= n Get 'n' characters of additional randomness from - user (min={g.min_urandchars}, max={g.max_urandchars}) --q, --quiet Suppress warnings; overwrite files without - prompting --v, --verbose Produce more verbose output -""".format(g=g), - 'notes': """ - -NOTE: The key ID will change if either the passphrase or hash preset are - changed -""" -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True -if 'verbose' in opts: g.verbose = True -if 'show_hash_presets' in opts: show_hash_presets() - -if len(cmd_args) != 1: - msg("One input file must be specified") - sys.exit(2) -infile = cmd_args[0] - -# Old key: -label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile) -seed_id,key_id = metadata[:2] - -# Repeat on incorrect pw entry -prompt = "Enter %spassphrase: " % ("" - if 'keep_old_passphrase' in opts else "old ") -while True: - passwd = get_mmgen_passphrase(prompt,{}) - key = make_key(passwd, salt, hash_preset) - seed = decrypt_seed(enc_seed, key, seed_id, key_id) - if seed: break - -changed = {} - -if 'label' in opts: - if opts['label'] != label: - msg("Label changed: '%s' -> '%s'" % (label, opts['label'])) - changed['label'] = True - else: - msg("Label is unchanged: '%s'" % (label)) -else: opts['label'] = label # Copy the old label - -if 'hash_preset' in opts: - if hash_preset != opts['hash_preset']: - qmsg("Hash preset has changed (%s -> %s)" % - (hash_preset, opts['hash_preset'])) - changed['preset'] = True - else: - msg("Hash preset is unchanged") -else: - opts['hash_preset'] = hash_preset - -if 'keep_old_passphrase' in opts: - msg("Keeping old passphrase by user request") -else: - new_passwd = get_new_passphrase("new passphrase", opts) - - if new_passwd == passwd: - qmsg("Passphrase is unchanged") - else: - qmsg("Passphrase has changed") - passwd = new_passwd - changed['passwd'] = True - -if 'preset' in changed or 'passwd' in changed: # Update key ID, salt - qmsg("Will update salt and key ID") - - from hashlib import sha256 - - salt = sha256(salt + get_random(128,opts)).digest()[:g.salt_len] - key = make_key(passwd, salt, opts['hash_preset']) - new_key_id = make_chksum_8(key) - qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id)) - key_id = new_key_id - enc_seed = encrypt_seed(seed, key) -elif not 'label' in changed: - msg("Data unchanged. No file will be written") - sys.exit(2) - -write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts) +import mmgen.main +mmgen.main.main("passchg") diff --git a/mmgen-pywallet b/mmgen-pywallet index 452940ed..fe88c2e0 100755 --- a/mmgen-pywallet +++ b/mmgen-pywallet @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013 by philemon +# Copyright (C)2013-2014 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 @@ -15,1664 +15,10 @@ # # 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 -# dump keys, addresses or keys for specified addresses (output in flat list) - -# PyWallet 1.2.1 (Public Domain) -# http://github.com/joric/pywallet -# Most of the actual PyWallet code placed in the public domain. -# PyWallet includes portions of free software, listed below. - -# BitcoinTools (wallet.dat handling code, MIT License) -# https://github.com/gavinandresen/bitcointools -# Copyright (c) 2010 Gavin Andresen - -# python-ecdsa (EC_KEY implementation, MIT License) -# http://github.com/warner/python-ecdsa -# "python-ecdsa" Copyright (c) 2010 Brian Warner -# Portions written in 2005 by Peter Pearson and placed in the public domain. - -# SlowAES (aes.py code, Apache 2 License) -# http://code.google.com/p/slowaes/ -# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org), -# Alex Martelli (http://www.aleax.it) -# Ported from C code written by Laurent Haan (http://www.progressive-coding.com) - -from bsddb.db import * -import sys, time -import json -import logging -import struct -import StringIO -import traceback -import socket -import types -import string -import exceptions -import hashlib -import random -import math - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.util import msg - -max_version = 60000 -addrtype = 0 -json_db = {} -private_keys = [] -password = None - -help_data = { - 'prog_name': g.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 --e, --echo-passphrase Display passphrase on screen upon entry --j, --json Dump wallet in json format --k, --keys Dump all private keys (flat list) --a, --addrs Dump all addresses (flat list) --K, --keysforaddrs= f Dump private keys for addresses listed in file 'f' --P, --passwd-file= f Get passphrase from file 'f' --S, --stdout Dump to stdout rather than file -""" -} - -opts,cmd_args = parse_opts(sys.argv,help_data) -from mmgen.Opts import warn_incompatible_opts -warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs')) - -if len(cmd_args) == 1: - from mmgen.util import check_infile - check_infile(cmd_args[0]) -else: - usage(help_data) - -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) - -def append_PKCS7_padding(s): - """return s padded to a multiple of 16-bytes by PKCS7 padding""" - numpads = 16 - (len(s)%16) - return s + numpads*chr(numpads) - -def strip_PKCS7_padding(s): - """return s stripped of PKCS7 padding""" - if len(s)%16 or not s: - raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) - numpads = ord(s[-1]) - if numpads > 16: - raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) - return s[:-numpads] - -class AES(object): - # valid key sizes - keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) - - # Rijndael S-box - sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, - 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, - 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, - 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, - 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, - 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, - 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, - 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, - 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, - 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, - 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, - 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, - 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, - 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, - 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, - 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, - 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, - 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, - 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, - 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, - 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, - 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, - 0x54, 0xbb, 0x16] - - # Rijndael Inverted S-box - rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, - 0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, - 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54, - 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, - 0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, - 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8, - 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, - 0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, - 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab, - 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, - 0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, - 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41, - 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, - 0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, - 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d, - 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b , - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, - 0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, - 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60, - 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, - 0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, - 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b, - 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, - 0x21, 0x0c, 0x7d] - - def getSBoxValue(self,num): - """Retrieves a given S-Box Value""" - return self.sbox[num] - - def getSBoxInvert(self,num): - """Retrieves a given Inverted S-Box Value""" - return self.rsbox[num] - - def rotate(self, word): - """ Rijndael's key schedule rotate operation. - - Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d - Word is an char list of size 4 (32 bits overall). - """ - return word[1:] + word[:1] - - # Rijndael Rcon - Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, - 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, - 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, - 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, - 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, - 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, - 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, - 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, - 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, - 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, - 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, - 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, - 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, - 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, - 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, - 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, - 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, - 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, - 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, - 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, - 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, - 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, - 0xe8, 0xcb ] - - def getRconValue(self, num): - """Retrieves a given Rcon Value""" - return self.Rcon[num] - - def core(self, word, iteration): - """Key schedule core.""" - # rotate the 32-bit word 8 bits to the left - word = self.rotate(word) - # apply S-Box substitution on all 4 parts of the 32-bit word - for i in range(4): - word[i] = self.getSBoxValue(word[i]) - # XOR the output of the rcon operation with i to the first part - # (leftmost) only - word[0] = word[0] ^ self.getRconValue(iteration) - return word - - def expandKey(self, key, size, expandedKeySize): - """Rijndael's key expansion. - - Expands an 128,192,256 key into an 176,208,240 bytes key - - expandedKey is a char list of large enough size, - key is the non-expanded key. - """ - # current expanded keySize, in bytes - currentSize = 0 - rconIteration = 1 - expandedKey = [0] * expandedKeySize - - # set the 16, 24, 32 bytes of the expanded key to the input key - for j in range(size): - expandedKey[j] = key[j] - currentSize += size - - while currentSize < expandedKeySize: - # assign the previous 4 bytes to the temporary value t - t = expandedKey[currentSize-4:currentSize] - - # every 16,24,32 bytes we apply the core schedule to t - # and increment rconIteration afterwards - if currentSize % size == 0: - t = self.core(t, rconIteration) - rconIteration += 1 - # For 256-bit keys, we add an extra sbox to the calculation - if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): - for l in range(4): t[l] = self.getSBoxValue(t[l]) - - # We XOR t with the four-byte block 16,24,32 bytes before the new - # expanded key. This becomes the next four bytes in the expanded - # key. - for m in range(4): - expandedKey[currentSize] = expandedKey[currentSize - size] ^ \ - t[m] - currentSize += 1 - - return expandedKey - - def addRoundKey(self, state, roundKey): - """Adds (XORs) the round key to the state.""" - for i in range(16): - state[i] ^= roundKey[i] - return state - - def createRoundKey(self, expandedKey, roundKeyPointer): - """Create a round key. - Creates a round key from the given expanded key and the - position within the expanded key. - """ - roundKey = [0] * 16 - for i in range(4): - for j in range(4): - roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j] - return roundKey - - def galois_multiplication(self, a, b): - """Galois multiplication of 8 bit characters a and b.""" - p = 0 - for counter in range(8): - if b & 1: p ^= a - hi_bit_set = a & 0x80 - a <<= 1 - # keep a 8 bit - a &= 0xFF - if hi_bit_set: - a ^= 0x1b - b >>= 1 - return p - - # - # substitute all the values from the state with the value in the SBox - # using the state value as index for the SBox - # - def subBytes(self, state, isInv): - if isInv: getter = self.getSBoxInvert - else: getter = self.getSBoxValue - for i in range(16): state[i] = getter(state[i]) - return state - - # iterate over the 4 rows and call shiftRow() with that row - def shiftRows(self, state, isInv): - for i in range(4): - state = self.shiftRow(state, i*4, i, isInv) - return state - - # each iteration shifts the row to the left by 1 - def shiftRow(self, state, statePointer, nbr, isInv): - for i in range(nbr): - if isInv: - state[statePointer:statePointer+4] = \ - state[statePointer+3:statePointer+4] + \ - state[statePointer:statePointer+3] - else: - state[statePointer:statePointer+4] = \ - state[statePointer+1:statePointer+4] + \ - state[statePointer:statePointer+1] - return state - - # galois multiplication of the 4x4 matrix - def mixColumns(self, state, isInv): - # iterate over the 4 columns - for i in range(4): - # construct one column by slicing over the 4 rows - column = state[i:i+16:4] - # apply the mixColumn on one column - column = self.mixColumn(column, isInv) - # put the values back into the state - state[i:i+16:4] = column - - return state - - # galois multiplication of 1 column of the 4x4 matrix - def mixColumn(self, column, isInv): - if isInv: mult = [14, 9, 13, 11] - else: mult = [2, 1, 1, 3] - cpy = list(column) - g = self.galois_multiplication - - column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ - g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) - column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ - g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) - column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ - g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) - column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ - g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) - return column - - # applies the 4 operations of the forward round in sequence - def aes_round(self, state, roundKey): - state = self.subBytes(state, False) - state = self.shiftRows(state, False) - state = self.mixColumns(state, False) - state = self.addRoundKey(state, roundKey) - return state - - # applies the 4 operations of the inverse round in sequence - def aes_invRound(self, state, roundKey): - state = self.shiftRows(state, True) - state = self.subBytes(state, True) - state = self.addRoundKey(state, roundKey) - state = self.mixColumns(state, True) - return state - - # Perform the initial operations, the standard round, and the final - # operations of the forward aes, creating a round key for each round - def aes_main(self, state, expandedKey, nbrRounds): - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) - i = 1 - while i < nbrRounds: - state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i)) - i += 1 - state = self.subBytes(state, False) - state = self.shiftRows(state, False) - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) - return state - - # Perform the initial operations, the standard round, and the final - # operations of the inverse aes, creating a round key for each round - def aes_invMain(self, state, expandedKey, nbrRounds): - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) - i = nbrRounds - 1 - while i > 0: - state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i)) - i -= 1 - state = self.shiftRows(state, True) - state = self.subBytes(state, True) - state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) - return state - - # encrypts a 128 bit input block against the given key of size specified - def encrypt(self, iput, key, size): - output = [0] * 16 - # the number of rounds - nbrRounds = 0 - # the 128 bit block to encode - block = [0] * 16 - # set the number of rounds - if size == self.keySize["SIZE_128"]: nbrRounds = 10 - elif size == self.keySize["SIZE_192"]: nbrRounds = 12 - elif size == self.keySize["SIZE_256"]: nbrRounds = 14 - else: return None - - # the expanded keySize - expandedKeySize = 16*(nbrRounds+1) - - # Set the block values, for the block: - # a0,0 a0,1 a0,2 a0,3 - # a1,0 a1,1 a1,2 a1,3 - # a2,0 a2,1 a2,2 a2,3 - # a3,0 a3,1 a3,2 a3,3 - # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 - # - # iterate over the columns - for i in range(4): - # iterate over the rows - for j in range(4): - block[(i+(j*4))] = iput[(i*4)+j] - - # expand the key into an 176, 208, 240 bytes key - # the expanded key - expandedKey = self.expandKey(key, size, expandedKeySize) - - # encrypt the block using the expandedKey - block = self.aes_main(block, expandedKey, nbrRounds) - - # unmap the block again into the output - for k in range(4): - # iterate over the rows - for l in range(4): - output[(k*4)+l] = block[(k+(l*4))] - return output - - # decrypts a 128 bit input block against the given key of size specified - def decrypt(self, iput, key, size): - output = [0] * 16 - # the number of rounds - nbrRounds = 0 - # the 128 bit block to decode - block = [0] * 16 - # set the number of rounds - if size == self.keySize["SIZE_128"]: nbrRounds = 10 - elif size == self.keySize["SIZE_192"]: nbrRounds = 12 - elif size == self.keySize["SIZE_256"]: nbrRounds = 14 - else: return None - - # the expanded keySize - expandedKeySize = 16*(nbrRounds+1) - - # Set the block values, for the block: - # a0,0 a0,1 a0,2 a0,3 - # a1,0 a1,1 a1,2 a1,3 - # a2,0 a2,1 a2,2 a2,3 - # a3,0 a3,1 a3,2 a3,3 - # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 - - # iterate over the columns - for i in range(4): - # iterate over the rows - for j in range(4): - block[(i+(j*4))] = iput[(i*4)+j] - # expand the key into an 176, 208, 240 bytes key - expandedKey = self.expandKey(key, size, expandedKeySize) - # decrypt the block using the expandedKey - block = self.aes_invMain(block, expandedKey, nbrRounds) - # unmap the block again into the output - for k in range(4): - # iterate over the rows - for l in range(4): - output[(k*4)+l] = block[(k+(l*4))] - return output - -class AESModeOfOperation(object): - - aes = AES() - - # structure of supported modes of operation - modeOfOperation = dict(OFB=0, CFB=1, CBC=2) - - # converts a 16 character string into a number array - def convertString(self, string, start, end, mode): - if end - start > 16: end = start + 16 - if mode == self.modeOfOperation["CBC"]: ar = [0] * 16 - else: ar = [] - - i = start - j = 0 - while len(ar) < end - start: - ar.append(0) - while i < end: - ar[j] = ord(string[i]) - j += 1 - i += 1 - return ar - - # Mode of Operation Encryption - # stringIn - Input String - # mode - mode of type modeOfOperation - # hexKey - a hex key of the bit length size - # size - the bit length of the key - # hexIV - the 128 bit hex Initilization Vector - def encrypt(self, stringIn, mode, key, size, IV): - if len(key) % size: - return None - if len(IV) % 16: - return None - # the AES input/output - plaintext = [] - iput = [0] * 16 - output = [] - ciphertext = [0] * 16 - # the output cipher string - cipherOut = [] - # char firstRound - firstRound = True - if stringIn != None: - for j in range(int(math.ceil(float(len(stringIn))/16))): - start = j*16 - end = j*16+16 - if end > len(stringIn): - end = len(stringIn) - plaintext = self.convertString(stringIn, start, end, mode) - # print 'PT@%s:%s' % (j, plaintext) - if mode == self.modeOfOperation["CFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(plaintext)-1 < i: - ciphertext[i] = 0 ^ output[i] - elif len(output)-1 < i: - ciphertext[i] = plaintext[i] ^ 0 - elif len(plaintext)-1 < i and len(output) < i: - ciphertext[i] = 0 ^ 0 - else: - ciphertext[i] = plaintext[i] ^ output[i] - for k in range(end-start): - cipherOut.append(ciphertext[k]) - iput = ciphertext - elif mode == self.modeOfOperation["OFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(plaintext)-1 < i: - ciphertext[i] = 0 ^ output[i] - elif len(output)-1 < i: - ciphertext[i] = plaintext[i] ^ 0 - elif len(plaintext)-1 < i and len(output) < i: - ciphertext[i] = 0 ^ 0 - else: - ciphertext[i] = plaintext[i] ^ output[i] - for k in range(end-start): - cipherOut.append(ciphertext[k]) - iput = output - elif mode == self.modeOfOperation["CBC"]: - for i in range(16): - if firstRound: - iput[i] = plaintext[i] ^ IV[i] - else: - iput[i] = plaintext[i] ^ ciphertext[i] - # print 'IP@%s:%s' % (j, iput) - firstRound = False - ciphertext = self.aes.encrypt(iput, key, size) - # always 16 bytes because of the padding for CBC - for k in range(16): - cipherOut.append(ciphertext[k]) - return mode, len(stringIn), cipherOut - - # Mode of Operation Decryption - # cipherIn - Encrypted String - # originalsize - The unencrypted string length - required for CBC - # mode - mode of type modeOfOperation - # key - a number array of the bit length size - # size - the bit length of the key - # IV - the 128 bit number array Initilization Vector - def decrypt(self, cipherIn, originalsize, mode, key, size, IV): - # cipherIn = unescCtrlChars(cipherIn) - if len(key) % size: - return None - if len(IV) % 16: - return None - # the AES input/output - ciphertext = [] - iput = [] - output = [] - plaintext = [0] * 16 - # the output plain text string - stringOut = '' - # char firstRound - firstRound = True - if cipherIn != None: - for j in range(int(math.ceil(float(len(cipherIn))/16))): - start = j*16 - end = j*16+16 - if j*16+16 > len(cipherIn): - end = len(cipherIn) - ciphertext = cipherIn[start:end] - if mode == self.modeOfOperation["CFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(output)-1 < i: - plaintext[i] = 0 ^ ciphertext[i] - elif len(ciphertext)-1 < i: - plaintext[i] = output[i] ^ 0 - elif len(output)-1 < i and len(ciphertext) < i: - plaintext[i] = 0 ^ 0 - else: - plaintext[i] = output[i] ^ ciphertext[i] - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = ciphertext - elif mode == self.modeOfOperation["OFB"]: - if firstRound: - output = self.aes.encrypt(IV, key, size) - firstRound = False - else: - output = self.aes.encrypt(iput, key, size) - for i in range(16): - if len(output)-1 < i: - plaintext[i] = 0 ^ ciphertext[i] - elif len(ciphertext)-1 < i: - plaintext[i] = output[i] ^ 0 - elif len(output)-1 < i and len(ciphertext) < i: - plaintext[i] = 0 ^ 0 - else: - plaintext[i] = output[i] ^ ciphertext[i] - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = output - elif mode == self.modeOfOperation["CBC"]: - output = self.aes.decrypt(ciphertext, key, size) - for i in range(16): - if firstRound: - plaintext[i] = IV[i] ^ output[i] - else: - plaintext[i] = iput[i] ^ output[i] - firstRound = False - if originalsize is not None and originalsize < end: - for k in range(originalsize-start): - stringOut += chr(plaintext[k]) - else: - for k in range(end-start): - stringOut += chr(plaintext[k]) - iput = ciphertext - return stringOut - -# end of aes.py code - -# pywallet crypter implementation - -crypter = None - -try: - from Crypto.Cipher import AES - crypter = 'pycrypto' -except: - pass - -class Crypter_pycrypto( object ): - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - data = vKeyData + vSalt - for i in xrange(nDerivIterations): - data = hashlib.sha512(data).digest() - self.SetKey(data[0:32]) - self.SetIV(data[32:32+16]) - return len(data) - - def SetKey(self, key): - self.chKey = key - - def SetIV(self, iv): - self.chIV = iv[0:16] - - def Encrypt(self, data): - return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32] - - def Decrypt(self, data): - return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32] - -try: - if not crypter: - import ctypes - import ctypes.util - ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32') - crypter = 'ssl' -except: - pass - -class Crypter_ssl(object): - def __init__(self): - self.chKey = ctypes.create_string_buffer (32) - self.chIV = ctypes.create_string_buffer (16) - - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - strKeyData = ctypes.create_string_buffer (vKeyData) - chSalt = ctypes.create_string_buffer (vSalt) - return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData, - len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV)) - - def SetKey(self, key): - self.chKey = ctypes.create_string_buffer(key) - - def SetIV(self, iv): - self.chIV = ctypes.create_string_buffer(iv) - - def Encrypt(self, data): - buf = ctypes.create_string_buffer(len(data) + 16) - written = ctypes.c_int(0) - final = ctypes.c_int(0) - ctx = ssl.EVP_CIPHER_CTX_new() - ssl.EVP_CIPHER_CTX_init(ctx) - ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) - ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) - output = buf.raw[:written.value] - ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)) - output += buf.raw[:final.value] - return output - - def Decrypt(self, data): - buf = ctypes.create_string_buffer(len(data) + 16) - written = ctypes.c_int(0) - final = ctypes.c_int(0) - ctx = ssl.EVP_CIPHER_CTX_new() - ssl.EVP_CIPHER_CTX_init(ctx) - ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) - ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) - output = buf.raw[:written.value] - ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)) - output += buf.raw[:final.value] - return output - -class Crypter_pure(object): - def __init__(self): - self.m = AESModeOfOperation() - self.cbc = self.m.modeOfOperation["CBC"] - self.sz = self.m.aes.keySize["SIZE_256"] - - def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): - if nDerivationMethod != 0: - return 0 - data = vKeyData + vSalt - for i in xrange(nDerivIterations): - data = hashlib.sha512(data).digest() - self.SetKey(data[0:32]) - self.SetIV(data[32:32+16]) - return len(data) - - def SetKey(self, key): - self.chKey = [ord(i) for i in key] - - def SetIV(self, iv): - self.chIV = [ord(i) for i in iv] - - def Encrypt(self, data): - mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV) - return ''.join(map(chr, cypher)) - - def Decrypt(self, data): - chData = [ord(i) for i in data] - return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV) - -# secp256k1 - -_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL -_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L -_b = 0x0000000000000000000000000000000000000000000000000000000000000007L -_a = 0x0000000000000000000000000000000000000000000000000000000000000000L -_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L -_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L - -# python-ecdsa code (EC_KEY implementation) - -class CurveFp( object ): - def __init__( self, p, a, b ): - self.__p = p - self.__a = a - self.__b = b - - def p( self ): - return self.__p - - def a( self ): - return self.__a - - def b( self ): - return self.__b - - def contains_point( self, x, y ): - return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 - -class Point( object ): - def __init__( self, curve, x, y, order = None ): - self.__curve = curve - self.__x = x - self.__y = y - self.__order = order - if self.__curve: assert self.__curve.contains_point( x, y ) - if order: assert self * order == INFINITY - - def __add__( self, other ): - if other == INFINITY: return self - if self == INFINITY: return other - assert self.__curve == other.__curve - if self.__x == other.__x: - if ( self.__y + other.__y ) % self.__curve.p() == 0: - return INFINITY - else: - return self.double() - - p = self.__curve.p() - l = ( ( other.__y - self.__y ) * \ - inverse_mod( other.__x - self.__x, p ) ) % p - x3 = ( l * l - self.__x - other.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - return Point( self.__curve, x3, y3 ) - - def __mul__( self, other ): - def leftmost_bit( x ): - assert x > 0 - result = 1L - while result <= x: result = 2 * result - return result / 2 - - e = other - if self.__order: e = e % self.__order - if e == 0: return INFINITY - if self == INFINITY: return INFINITY - assert e > 0 - e3 = 3 * e - negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) - i = leftmost_bit( e3 ) / 2 - result = self - while i > 1: - result = result.double() - if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self - if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self - i = i / 2 - return result - - def __rmul__( self, other ): - return self * other - - def __str__( self ): - if self == INFINITY: return "infinity" - return "(%d,%d)" % ( self.__x, self.__y ) - - def double( self ): - if self == INFINITY: - return INFINITY - - p = self.__curve.p() - a = self.__curve.a() - l = ( ( 3 * self.__x * self.__x + a ) * \ - inverse_mod( 2 * self.__y, p ) ) % p - x3 = ( l * l - 2 * self.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - return Point( self.__curve, x3, y3 ) - - def x( self ): - return self.__x - - def y( self ): - return self.__y - - def curve( self ): - return self.__curve - - def order( self ): - return self.__order - -INFINITY = Point( None, None, None ) - -def inverse_mod( a, m ): - if a < 0 or m <= a: a = a % m - c, d = a, m - uc, vc, ud, vd = 1, 0, 0, 1 - while c != 0: - q, c, d = divmod( d, c ) + ( c, ) - uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc - assert d == 1 - if ud > 0: return ud - else: return ud + m - -class Signature( object ): - def __init__( self, r, s ): - self.r = r - self.s = s - -class Public_key( object ): - def __init__( self, generator, point ): - self.curve = generator.curve() - self.generator = generator - self.point = point - n = generator.order() - if not n: - raise RuntimeError, "Generator point must have order." - if not n * point == INFINITY: - raise RuntimeError, "Generator point order is bad." - if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): - raise RuntimeError, "Generator point has x or y out of range." - - def verifies( self, hash, signature ): - G = self.generator - n = G.order() - r = signature.r - s = signature.s - if r < 1 or r > n-1: return False - if s < 1 or s > n-1: return False - c = inverse_mod( s, n ) - u1 = ( hash * c ) % n - u2 = ( r * c ) % n - xy = u1 * G + u2 * self.point - v = xy.x() % n - return v == r - -class Private_key( object ): - def __init__( self, public_key, secret_multiplier ): - self.public_key = public_key - self.secret_multiplier = secret_multiplier - - def der( self ): - hex_der_key = '06052b8104000a30740201010420' + \ - '%064x' % self.secret_multiplier + \ - 'a00706052b8104000aa14403420004' + \ - '%064x' % self.public_key.point.x() + \ - '%064x' % self.public_key.point.y() - return hex_der_key.decode('hex') - - def sign( self, hash, random_k ): - G = self.public_key.generator - n = G.order() - k = random_k % n - p1 = k * G - r = p1.x() - if r == 0: raise RuntimeError, "amazingly unlucky random number r" - s = ( inverse_mod( k, n ) * \ - ( hash + ( self.secret_multiplier * r ) % n ) ) % n - if s == 0: raise RuntimeError, "amazingly unlucky random number s" - return Signature( r, s ) - -class EC_KEY(object): - def __init__( self, secret ): - curve = CurveFp( _p, _a, _b ) - generator = Point( curve, _Gx, _Gy, _r ) - self.pubkey = Public_key( generator, generator * secret ) - self.privkey = Private_key( self.pubkey, secret ) - self.secret = secret - -# end of python-ecdsa code - -# pywallet openssl private key implementation - -def i2d_ECPrivateKey(pkey, compressed=False): - if compressed: - key = '3081d30201010420' + \ - '%064x' % pkey.secret + \ - 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ - '%064x' % _p + \ - '3006040100040107042102' + \ - '%064x' % _Gx + \ - '022100' + \ - '%064x' % _r + \ - '020101a124032200' - else: - key = '308201130201010420' + \ - '%064x' % pkey.secret + \ - 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ - '%064x' % _p + \ - '3006040100040107044104' + \ - '%064x' % _Gx + \ - '%064x' % _Gy + \ - '022100' + \ - '%064x' % _r + \ - '020101a144034200' - - return key.decode('hex') + i2o_ECPublicKey(pkey, compressed) - -def i2o_ECPublicKey(pkey, compressed=False): - # public keys are 65 bytes long (520 bits) - # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate - # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed - # compressed keys: where is 0x02 if y is even and 0x03 if y is odd - if compressed: - if pkey.pubkey.point.y() & 1: - key = '03' + '%064x' % pkey.pubkey.point.x() - else: - key = '02' + '%064x' % pkey.pubkey.point.x() - else: - key = '04' + \ - '%064x' % pkey.pubkey.point.x() + \ - '%064x' % pkey.pubkey.point.y() - - return key.decode('hex') - -# bitcointools hashes and base58 implementation - -def hash_160(public_key): - md = hashlib.new('ripemd160') - md.update(hashlib.sha256(public_key).digest()) - return md.digest() - -def public_key_to_bc_address(public_key): - h160 = hash_160(public_key) - return hash_160_to_bc_address(h160) - -def hash_160_to_bc_address(h160): - vh160 = chr(addrtype) + h160 - h = Hash(vh160) - addr = vh160 + h[0:4] - return b58encode(addr) - -def bc_address_to_hash_160(addr): - bytes = b58decode(addr, 25) - return bytes[1:21] - -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -__b58base = len(__b58chars) - -def b58encode(v): - """ encode v, which is a string of bytes, to base58. - """ - - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += (256**i) * ord(c) - - result = '' - while long_value >= __b58base: - div, mod = divmod(long_value, __b58base) - result = __b58chars[mod] + result - long_value = div - result = __b58chars[long_value] + result - - # Bitcoin does a little leading-zero-compression: - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == '\0': nPad += 1 - else: break - - return (__b58chars[0]*nPad) + result - -def b58decode(v, length): - """ decode v into a string of len bytes - """ - long_value = 0L - for (i, c) in enumerate(v[::-1]): - long_value += __b58chars.find(c) * (__b58base**i) - - result = '' - while long_value >= 256: - div, mod = divmod(long_value, 256) - result = chr(mod) + result - long_value = div - result = chr(long_value) + result - - nPad = 0 - for c in v: - if c == __b58chars[0]: nPad += 1 - else: break - - result = chr(0)*nPad + result - if length is not None and len(result) != length: - return None - - return result - -# end of bitcointools base58 implementation - - -# address handling code - -def Hash(data): - return hashlib.sha256(hashlib.sha256(data).digest()).digest() - -def EncodeBase58Check(secret): - hash = Hash(secret) - return b58encode(secret + hash[0:4]) - -def DecodeBase58Check(sec): - vchRet = b58decode(sec, None) - secret = vchRet[0:-4] - csum = vchRet[-4:] - hash = Hash(secret) - cs32 = hash[0:4] - if cs32 != csum: - return None - else: - return secret - -def PrivKeyToSecret(privkey): - if len(privkey) == 279: - return privkey[9:9+32] - else: - return privkey[8:8+32] - -def SecretToASecret(secret, compressed=False): - vchIn = chr((addrtype+128)&255) + secret - if compressed: vchIn += '\01' - return EncodeBase58Check(vchIn) - -def ASecretToSecret(sec): - vch = DecodeBase58Check(sec) - if vch and vch[0] == chr((addrtype+128)&255): - return vch[1:] - else: - return False - -def regenerate_key(sec): - b = ASecretToSecret(sec) - if not b: - return False - b = b[0:32] - secret = int('0x' + b.encode('hex'), 16) - return EC_KEY(secret) - -def GetPubKey(pkey, compressed=False): - return i2o_ECPublicKey(pkey, compressed) - -def GetPrivKey(pkey, compressed=False): - return i2d_ECPrivateKey(pkey, compressed) - -def GetSecret(pkey): - return ('%064x' % pkey.secret).decode('hex') - -def is_compressed(sec): - b = ASecretToSecret(sec) - return len(b) == 33 - -# bitcointools wallet.dat handling code - -def create_env(db_dir): - db_env = DBEnv(0) - r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER)) - return db_env - -def parse_CAddress(vds): - d = {'ip':'0.0.0.0','port':0,'nTime': 0} - try: - d['nVersion'] = vds.read_int32() - d['nTime'] = vds.read_uint32() - d['nServices'] = vds.read_uint64() - d['pchReserved'] = vds.read_bytes(12) - d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) - d['port'] = vds.read_uint16() - except: - pass - return d - -def deserialize_CAddress(d): - return d['ip']+":"+str(d['port']) - -def parse_BlockLocator(vds): - d = { 'hashes' : [] } - nHashes = vds.read_compact_size() - for i in xrange(nHashes): - d['hashes'].append(vds.read_bytes(32)) - return d - -def deserialize_BlockLocator(d): - result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') - return result - -def parse_setting(setting, vds): - if setting[0] == "f": # flag (boolean) settings - return str(vds.read_boolean()) - elif setting[0:4] == "addr": # CAddress - d = parse_CAddress(vds) - return deserialize_CAddress(d) - elif setting == "nTransactionFee": - return vds.read_int64() - elif setting == "nLimitProcessors": - return vds.read_int32() - return 'unknown setting' - -class SerializationError(Exception): - """ Thrown when there's a problem deserializing or serializing """ - -class BCDataStream(object): - def __init__(self): - self.input = None - self.read_cursor = 0 - - def clear(self): - self.input = None - self.read_cursor = 0 - - def write(self, bytes): # Initialize with string of bytes - if self.input is None: - self.input = bytes - else: - self.input += bytes - - def map_file(self, file, start): # Initialize with bytes from file - self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) - self.read_cursor = start - def seek_file(self, position): - self.read_cursor = position - def close_file(self): - self.input.close() - - def read_string(self): - # Strings are encoded depending on length: - # 0 to 252 : 1-byte-length followed by bytes (if any) - # 253 to 65,535 : byte'253' 2-byte-length followed by bytes - # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes - # ... and the Bitcoin client is coded to understand: - # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string - # ... but I don't think it actually handles any strings that big. - if self.input is None: - raise SerializationError("call write(bytes) before trying to deserialize") - - try: - length = self.read_compact_size() - except IndexError: - raise SerializationError("attempt to read past end of buffer") - - return self.read_bytes(length) - - def write_string(self, string): - # Length-encoded as with read-string - self.write_compact_size(len(string)) - self.write(string) - - def read_bytes(self, length): - try: - result = self.input[self.read_cursor:self.read_cursor+length] - self.read_cursor += length - return result - except IndexError: - raise SerializationError("attempt to read past end of buffer") - - return '' - - def read_boolean(self): return self.read_bytes(1)[0] != chr(0) - def read_int16(self): return self._read_num(' max_version: - print "Version mismatch (must be <= %d)" % max_version - exit(1) - -wallet_addrs = [i['addr'] for i in json_db['keys']] - -if 'json' in opts: - data = [json.dumps(json_db, sort_keys=True, indent=4)] - ext,what = "json","json dump" - -elif 'keys' in opts: - data = sorted([i['sec'] for i in json_db['keys']]) - ext,what = "keys","private keys" - -elif 'addrs' in opts: - data = sorted([i['addr'] for i in json_db['keys']]) - ext,what = "addrs","addresses" - -elif 'keysforaddrs' in opts: - from mmgen.util import get_lines_from_file - usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_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" \ - else "%s:%s" % (len(data),len(wallet_addrs)) - -from mmgen.util import make_chksum_8,write_walletdat_dump_to_file,write_to_stdout -wallet_id = make_chksum_8(str(sorted(wallet_addrs))) - -data = "\n".join(data) + "\n" - -# Output data -if 'stdout' in opts: - 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) -else: - write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts) +import mmgen.main +mmgen.main.main("pywallet") diff --git a/mmgen-tool b/mmgen-tool index c0c76d03..cdb64326 100755 --- a/mmgen-tool +++ b/mmgen-tool @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013 by philemon +# Copyright (C)2013-2014 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 @@ -15,57 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-tool: Perform various Bitcoin-related operations - part of the MMGen suite +mmgen-tool: Perform various Bitcoin-related operations. + Part of the MMGen suite """ -import sys -import mmgen.config as g -import mmgen.tool as tool -from mmgen.Opts import * - -help_data = { - 'prog_name': g.prog_name, - 'desc': "Perform various BTC-related operations", - 'usage': "[opts] ", - 'options': """ --d, --outdir= d Specify an alternate directory 'd' for output --h, --help Print this help message --q, --quiet Produce quieter output --r, --usr-randchars=n Get 'n' characters of additional randomness from - user (min={g.min_urandchars}, max={g.max_urandchars}) --v, --verbose Produce more verbose output -""".format(g=g), - 'notes': """ - -COMMANDS:{} -Type '{} --help for usage information on a particular -command -""".format(tool.command_help,g.prog_name) -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True -if 'verbose' in opts: g.verbose = True - -if len(cmd_args) < 1: - usage(help_data) - sys.exit(1) - -command = cmd_args.pop(0) - -if command not in tool.commands.keys(): - msg("'%s': No such command" % command) - sys.exit(1) - -if cmd_args and cmd_args[0] == '--help': - tool.tool_usage(g.prog_name, command) - sys.exit(0) - -args = tool.process_args(g.prog_name, command, cmd_args) - -tool.opts = opts - -#print command + "(" + ", ".join(args) + ")" -eval("tool." + command + "(" + ", ".join(args) + ")") +import mmgen.main +mmgen.main.main("tool") diff --git a/mmgen-txcreate b/mmgen-txcreate index a6590ad9..1714ef0b 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,207 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-txcreate: Create a BTC transaction, sending to specified addresses +mmgen-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs + to MMGen- or non-MMGen outputs """ -import sys -from decimal import Decimal - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.license import * -from mmgen.tx import * -from mmgen.util import msg, msg_r, user_confirm - -help_data = { - 'prog_name': g.prog_name, - 'desc': "Create a BTC transaction with outputs to specified addresses", - 'usage': "[opts] ... [change addr] [addr file] ...", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --e, --echo-passphrase Print passphrase to screen when typing it --f, --tx-fee= f Transaction fee (default: {g.tx_fee} BTC) --i, --info Display unspent outputs and exit --q, --quiet Suppress warnings; overwrite files without - prompting -""".format(g=g), - 'notes': """ - -Transaction inputs are chosen from a list of the user's unpent outputs -via an interactive menu. - -Ages of transactions are approximate based on an average block creation -interval of {g.mins_per_block} minutes. - -Addresses on the command line can be Bitcoin addresses or {pnm} addresses -of the form :. - -To send all inputs (minus TX fee) to a single output, specify one address -with no amount on the command line. -""".format(g=g,pnm=g.proj_name) -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True - -if g.debug: show_opts_and_cmd_args(opts,cmd_args) - -c = connect_to_bitcoind() - -if not 'info' in opts: - do_license_msg(immed=True) - - tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],"" - - addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext] - cmd_args = set(cmd_args) - set(addrfiles) - - for a in addrfiles: - check_infile(a) - addr_data.append(parse_addrs_file(a)) - - def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map): - # assume mmaddr has already been checked - btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data) - if not btcaddr: - if addr_data: - btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data) - else: - msg(txmsg['addrfile_no_data_msg'] % mmaddr) - sys.exit(2) - - b2m_map[btcaddr] = mmaddr,label - return btcaddr - - for a in cmd_args: - if "," in a: - a1,a2 = a.split(",") - if is_btc_addr(a1): - btcaddr = a1 - elif is_mmgen_addr(a1): - btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map) - else: - msg("%s: unrecognized subargument in argument '%s'" % (a1,a)) - sys.exit(2) - - if is_btc_amt(a2): - tx_out[btcaddr] = normalize_btc_amt(a2) - else: - msg("%s: invalid amount in argument '%s'" % (a2,a)) - sys.exit(2) - elif is_mmgen_addr(a) or is_btc_addr(a): - if change_addr: - msg("ERROR: More than one change address specified: %s, %s" % - (change_addr, a)) - sys.exit(2) - change_addr = a if is_btc_addr(a) else \ - mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map) - tx_out[change_addr] = 0 - else: - msg("%s: unrecognized argument" % a) - sys.exit(2) - - if not tx_out: - msg("At least one output must be specified on the command line") - sys.exit(2) - - tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee - tx_fee = normalize_btc_amt(tx_fee) - if tx_fee > g.max_tx_fee: - msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee)) - sys.exit(2) - -if g.debug: show_opts_and_cmd_args(opts,cmd_args) - -#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit() - -#if False: -if g.bogus_wallet_data: - import mmgen.rpc - us = eval(get_data_from_file(g.bogus_wallet_data)) -else: - us = c.listunspent() - -if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2) - -unspent = sort_and_view(us,opts) - -total = trim_exponent(sum([i.amount for i in unspent])) - -msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent))) -if 'info' in opts: sys.exit(0) - -send_amt = sum([tx_out[i] for i in tx_out.keys()]) -msg("Total amount to spend: %s%s" % ( - (send_amt or "Unknown")," BTC" if send_amt else "")) - -while True: - sel_nums = select_outputs(unspent, - "Enter a range or space-separated list of outputs to spend: ") - msg("Selected output%s: %s" % - (("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums)) - ) - sel_unspent = [unspent[i-1] for i in sel_nums] - - mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent]) - mmaddrs.discard("") - - if mmaddrs and len(mmaddrs) < len(sel_unspent): - msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs))) - if not user_confirm("Accept?"): - continue - - total_in = trim_exponent(sum([i.amount for i in sel_unspent])) - change = trim_exponent(total_in - (send_amt + tx_fee)) - - if change >= 0: - prompt = "Transaction produces %s BTC in change. OK?" % change - if user_confirm(prompt,default_yes=True): - break - else: - msg(txmsg['not_enough_btc'] % change) - -if change > 0 and not change_addr: - msg(txmsg['throwaway_change'] % change) - sys.exit(2) - -if change_addr in tx_out and not change: - msg("Warning: Change address will be unused as transaction produces no change") - del tx_out[change_addr] - -for k,v in tx_out.items(): tx_out[k] = float(v) - -if change > 0: tx_out[change_addr] = float(change) - -tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent] - -if g.debug: - print "tx_in:", repr(tx_in) - print "tx_out:", repr(tx_out) - -tx_hex = c.createrawtransaction(tx_in,tx_out) -qmsg("Transaction successfully created") -prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager" -reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) - -if reply and reply in "YyVv": - pager = True if reply in "Vv" else False - view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager) - -prompt = "Save transaction?" -if user_confirm(prompt,default_yes=True): - amt = send_amt or change - tx_id = make_chksum_6(unhexlify(tx_hex)).upper() - outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) - data = "{} {} {}\n{}\n{}\n{}\n".format( - tx_id, amt, make_timestamp(), - tx_hex, - repr([i.__dict__ for i in sel_unspent]), - repr(b2m_map) - ) - write_to_file(outfile,data,opts,"transaction",False,True) -else: - msg("Transaction not saved") +import mmgen.main +mmgen.main.main("txcreate") diff --git a/mmgen-txsend b/mmgen-txsend index 89a6ab78..e4866b8f 100755 --- a/mmgen-txsend +++ b/mmgen-txsend @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,72 +15,10 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-txsend: Broadcast a Bitcoin transaction to the network +mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network """ -import sys - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.license import * -from mmgen.tx import * -from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit - -help_data = { - 'prog_name': g.prog_name, - 'desc': "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()), - 'usage': "[opts] ", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --q, --quiet Suppress warnings; overwrite files without prompting -""" -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True - -if len(cmd_args) == 1: - infile = cmd_args[0]; check_infile(infile) -else: usage(help_data) - -# Begin execution - -do_license_msg() - -tx_data = get_lines_from_file(infile,"signed transaction data") - -metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile) - -qmsg("Signed transaction file '%s' is valid" % infile) - -c = connect_to_bitcoind() - -prompt = "View transaction data? (y)es, (N)o, (v)iew in pager" -reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) -if reply and reply in "YyVv": - p = True if reply in "Vv" else False - view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p) - -warn = "Once this transaction is sent, there's no taking it back!" -what = "broadcast this transaction to the network" -expect = "YES, I REALLY WANT TO DO THIS" - -if g.quiet: warn,expect = "","YES" - -confirm_or_exit(warn, what, expect) - -msg("Sending transaction") - -try: - tx_id = c.sendrawtransaction(tx_hex) -except: - msg("Unable to send transaction") - sys.exit(3) - -msg("Transaction sent: %s" % tx_id) - -of = "tx_{}[{}].out".format(*metadata[:2]) -write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True) +import mmgen.main +mmgen.main.main("txsend") diff --git a/mmgen-txsign b/mmgen-txsign index 55facf58..cd358638 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,198 +15,10 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-txsign: Sign a Bitcoin transaction generated by mmgen-txcreate +mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate' """ -import sys - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.license import * -from mmgen.tx import * -from mmgen.util import msg,qmsg - -help_data = { - 'prog_name': g.prog_name, - 'desc': "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()), - 'usage': "[opts] .. [mmgen wallet/seed/words/brainwallet file] .. [addrfile] ..", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --e, --echo-passphrase Print passphrase to screen when typing it --i, --info Display information about the transaction and exit --I, --tx-id Display transaction ID and exit --k, --keys-from-file= f Provide additional keys for non-{pnm} addresses --K, --all-keys-from-file=f Like '-k', only use the keyfile as key source - for ALL inputs, including {pnm} ones. Can be used - for online signing without an {pnm} seed source. - {pnm}-to-BTC mappings can optionally be verified - using address file(s) listed on the command line --P, --passwd-file= f Get passphrase from file 'f' --q, --quiet Suppress warnings; overwrite files without - prompting --V, --skip-key-preverify Skip optional key pre-verification step --b, --from-brain= l,p Generate keys from a user-created password, - i.e. a "brainwallet", using seed length 'l' and - hash preset 'p' --w, --use-wallet-dat Get keys from a running bitcoind --g, --from-incog Generate keys from an incognito wallet --X, --from-incog-hex Generate keys from an incognito hexadecimal wallet --G, --from-incog-hidden= f,o,l Generate keys from incognito data in file - 'f' at offset 'o', with seed length of 'l' --m, --from-mnemonic Generate keys from an electrum-like mnemonic --s, --from-seed Generate keys from a seed in .{g.seed_ext} format -""".format(g=g,pnm=g.proj_name), - 'notes': """ - -Transactions with either {pnm} or non-{pnm} input addresses may be signed. -For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source. -For {pnm} inputs, key data is generated from your seed as with the -{pnl}-addrgen and {pnl}-keygen utilities. - -Data for the --from- options will be taken from a file if a second -file is specified on the command line. Otherwise, the user will be -prompted to enter the data. - -In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm} -keys must be supplied in a separate file (WIF format, one key per line) -using the '--keys-from-file' option. Alternatively, one may get keys from -a running bitcoind using the '--force-wallet-dat' option. First import the -required {pnm} keys using 'bitcoind importprivkey'. - -For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address -mappings are verified. Therefore, seed material for these addresses must -be supplied on the command line (but see '--all-keys-from-file'). - -Seed data supplied in files must have the following extensions: - wallet: '.{g.wallet_ext}' - seed: '.{g.seed_ext}' - mnemonic: '.{g.mn_ext}' - brainwallet: '.{g.brain_ext}' -""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower()) -} - -opts,infiles = parse_opts(sys.argv,help_data) - -for l in ( -('tx_id', 'info'), -('keys_from_file','all_keys_from_file') -): warn_incompatible_opts(opts,l) - -if "quiet" in opts: g.quiet = True -if 'from_incog_hex' in opts or 'from_incog_hidden' in opts: - opts['from_incog'] = True -if 'all_keys_from_file' in opts: - opts['keys_from_file'] = opts['all_keys_from_file'] - opts['skip_key_preverify'] = True - -if not infiles: usage(help_data) -for i in infiles: check_infile(i) - -c = connect_to_bitcoind() - -saved_seeds = {} -tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext] -addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext] -infiles = list(set(infiles) - set(tx_files) - set(addrfiles)) - -if not "info" in opts: do_license_msg(immed=True) - -if 'keys_from_file' in opts: - from mmgen.crypto import mmgen_decrypt - fn = opts['keys_from_file'] - if get_extension(fn) == g.mmenc_ext: - enc_d = get_data_from_file(fn,"encrypted keylist") - dec_d = mmgen_decrypt(enc_d,"",opts) - if dec_d: - keys_from_file = remove_comments(dec_d.split("\n")) - else: - msg("Decryption of encrypted keylist failed") - sys.exit(2) - else: - keys_from_file = get_lines_from_file(fn,"key data",trim_comments=True) -else: keys_from_file = [] - -for tx_file in tx_files: - m = "" if 'tx_id' in opts else "transaction data" - tx_data = get_lines_from_file(tx_file,m) - - metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file) - qmsg("Successfully opened transaction file '%s'" % tx_file) - - if 'tx_id' in opts: - msg(metadata[0]) - sys.exit(0) - - if 'info' in opts: - view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata) - sys.exit(0) - -# Are inputs mmgen addresses? - mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]] - other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]] - - if 'all_keys_from_file' in opts: other_inputs = inputs_data - - keys = keys_from_file - - if other_inputs and not keys and not 'use_wallet_dat' in opts: - missing_keys_errormsg(other_inputs) - sys.exit(2) - - if other_inputs and keys and not 'skip_key_preverify' in opts: - a = [i['address'] for i in other_inputs] - preverify_keys(a, keys) - opts['skip_key_preverify'] = True - - if 'all_keys_from_file' in opts: - if addrfiles: - check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles) - else: - confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue") - else: - check_mmgen_to_btc_addr_mappings( - mmgen_inputs,b2m_map,infiles,saved_seeds,opts) - - if len(tx_files) > 1: - msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files))) - - prompt = "View transaction data? (y)es, (N)o, (v)iew in pager" - reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) - if reply and reply in "YyVv": - p = True if reply in "Vv" else False - view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p) - - sig_data = [ - {"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']} - for i in inputs_data] - - if mmgen_inputs and not 'all_keys_from_file' in opts: - ml = [i['account'].split()[0] for i in mmgen_inputs] - keys += get_keys_for_mmgen_addrs(ml,infiles,saved_seeds,opts) - - if 'use_wallet_dat' in opts: - sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) - else: - sig_tx = sign_transaction(c,tx_hex,sig_data,keys) - elif other_inputs: - if keys: - sig_tx = sign_transaction(c,tx_hex,sig_data,keys) - else: - sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) - - if sig_tx['complete']: - prompt = "OK\nSave signed transaction?" - if user_confirm(prompt,default_yes=True): - outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext) - data = "{}\n{}\n{}\n{}\n".format( - " ".join(metadata[:2] + [make_timestamp()]), - sig_tx['hex'], - repr(inputs_data), - repr(b2m_map) - ) - write_to_file(outfile,data,opts,"signed transaction",True,True) - else: - msg("failed\nSome keys were missing. Transaction could not be signed.") - sys.exit(3) +import mmgen.main +mmgen.main.main("txsign") diff --git a/mmgen-walletchk b/mmgen-walletchk index 637fe834..fc46396e 100755 --- a/mmgen-walletchk +++ b/mmgen-walletchk @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,102 +15,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-walletchk: Check integrity of a mmgen deterministic wallet, display - information about it and export seed and mnemonic data +mmgen-walletchk: Check integrity of an MMGen deterministic wallet, display + information about it and export it to various formats """ -import sys -import mmgen.config as g -from mmgen.Opts import * -from mmgen.util import * -from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data - -help_data = { - 'prog_name': g.prog_name, - 'desc': """Check integrity of an {} deterministic wallet, display - its information, and export seed and mnemonic data. - """.format(g.proj_name), - 'usage': "[opts] [filename]", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --e, --echo-passphrase Print passphrase to screen when typing it --P, --passwd-file= f Get passphrase from file 'f' --q, --quiet Suppress warnings; overwrite files without prompting --r, --usr-randchars= n Get 'n' characters of additional randomness from - user (min={g.min_urandchars}, max={g.max_urandchars}) --S, --stdout Print seed or mnemonic data to standard output --v, --verbose Produce more verbose output --g, --export-incog Export wallet to incognito format --X, --export-incog-hex Export wallet to incognito hexadecimal format --G, --export-incog-hidden=f,o Hide incognito data in existing file 'f' - at offset 'o' (comma-separated) --m, --export-mnemonic Export the wallet's mnemonic to file --s, --export-seed Export the wallet's seed to file -""".format(g=g), - 'notes': """ - -Since good randomness is particularly important for incognito wallets, -the '--usr-randchars' option is turned on by default to gather additional -entropy from the user when one of the '--export-incog*' options is -selected. If you fully trust your OS's random number generator and wish -to disable this option, then specify '-r0' on the command line. -""" -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True -if 'verbose' in opts: g.verbose = True -if 'export_incog_hidden' in opts or 'export_incog_hex' in opts: - opts['export_incog'] = True - -if len(cmd_args) != 1: usage(help_data) - -check_infile(cmd_args[0]) - -if 'export_mnemonic' in opts: - qmsg("Exporting mnemonic data to file by user request") -elif 'export_seed' in opts: - qmsg("Exporting seed data to file by user request") -elif 'export_incog' in opts: - if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl - qmsg("Exporting wallet to incognito format by user request") - incog_enc,seed_id,key_id,iv_id,preset = \ - wallet_to_incog_data(cmd_args[0],opts) - - if "export_incog_hidden" in opts: - export_to_hidden_incog(incog_enc,opts) - else: - seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8 - fn = "%s-%s-%s[%s,%s].%s" % ( - seed_id, key_id, iv_id, seed_len, preset, - g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext - ) - data = pretty_hexdump(incog_enc,2,8,line_nums=False) \ - if "export_incog_hex" in opts else incog_enc - export_to_file(fn, data, opts, "incognito wallet data") - - sys.exit() - -seed = get_seed_from_wallet(cmd_args[0], opts) -if seed: qmsg("Wallet is OK") -else: - msg("Error opening wallet") - sys.exit(2) - -if 'export_mnemonic' in opts: - wl = get_default_wordlist() - from mmgen.mnemonic import get_mnemonic_from_seed - p = True if g.debug else False - mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p) - fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext) - export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data") - -elif 'export_seed' in opts: - from mmgen.bitcoin import b58encode_pad - data = col4(b58encode_pad(seed)) - chk = make_chksum_6(b58encode_pad(seed)) - fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext) - export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data") +import mmgen.main +mmgen.main.main("walletchk") diff --git a/mmgen-walletgen b/mmgen-walletgen index ddeeb01c..24282f86 100755 --- a/mmgen-walletgen +++ b/mmgen-walletgen @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,136 +15,10 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mmgen-walletgen: Generate a mmgen deterministic wallet +mmgen-walletgen: Generate an MMGen deterministic wallet """ -import sys, os -from hashlib import sha256 - -import mmgen.config as g -from mmgen.Opts import * -from mmgen.license import * -from mmgen.util import * -from mmgen.crypto import * - -help_data = { - 'prog_name': g.prog_name, - 'desc': "Generate an {} deterministic wallet".format(g.proj_name), - 'usage': "[opts] [infile]", - 'options': """ --h, --help Print this help message --d, --outdir= d Specify an alternate directory 'd' for output --e, --echo-passphrase Print passphrase to screen when typing it --H, --show-hash-presets Show information on available hash presets --l, --seed-len= n Create seed of length 'n'. Options: {seed_lens} - (default: {g.seed_len}) --L, --label= l Label to identify this wallet (32 chars max. - Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".") --p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' - (default: '{g.hash_preset}') --P, --passwd-file= f Get passphrase from file 'f' --q, --quiet Produce quieter output; overwrite files without - prompting --r, --usr-randchars= n Get 'n' characters of additional randomness from - user (min={g.min_urandchars}, max={g.max_urandchars}) --v, --verbose Produce more verbose output - --b, --from-brain= l,p Generate wallet from a user-created passphrase, - i.e. a "brainwallet", using seed length 'l' and - hash preset 'p' (comma-separated) --g, --from-incog Generate wallet from an incognito-format wallet --m, --from-mnemonic Generate wallet from an Electrum-like mnemonic --s, --from-seed Generate wallet from a seed in .{g.seed_ext} format -""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g), - 'notes': """ - -By default (i.e. when invoked without any of the '--from-' options), -{g.prog_name} generates a wallet based on a random seed. - -Data for the --from- options will be taken from if -is specified. Otherwise, the user will be prompted to enter the data. - -For passphrases all combinations of whitespace are equal, and leading and -trailing space are ignored. This permits reading passphrase data from a -multi-line file with free spacing and indentation. This is particularly -convenient for long brainwallet passphrases, for example. - -Since good randomness is particularly important when generating wallets, -the '--usr-randchars' option is turned on by default to gather additional -entropy from the user. If you fully trust your OS's random number gener- -ator and wish to disable this option, specify '-r0' on the command line. - -BRAINWALLET NOTE: - -As brainwallets require especially strong hashing to thwart dictionary -attacks, the brainwallet hash preset must be specified by the user, using -the 'p' parameter of the '--from-brain' option. This preset should be -stronger than the one used for hashing the seed (i.e. the default value or -the one specified in the '--hash-preset' option). - -The '--from-brain' option also requires the user to specify a seed length -(the 'l' parameter), which overrides both the default and any one given in -the '--seed-len' option. - -For a brainwallet passphrase to always generate the same keys and -addresses, the same 'l' and 'p' parameters to '--from-brain' must be used -in all future invocations with that passphrase. -""".format(g=g) -} - -opts,cmd_args = parse_opts(sys.argv,help_data) - -if 'quiet' in opts: g.quiet = True -if 'verbose' in opts: g.verbose = True -if 'show_hash_presets' in opts: show_hash_presets() -if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl - -if g.debug: show_opts_and_cmd_args(opts,cmd_args) - -if len(cmd_args) == 1: - infile = cmd_args[0] - check_infile(infile) - ext = infile.split(".")[-1] - ok_exts = g.seedfile_exts - for e in ok_exts: - if e == ext: break - else: - msg( -"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts)) - sys.exit(1) -elif len(cmd_args) == 0: - infile = "" -else: usage(help_data) - -# Begin execution - -do_license_msg() - -if 'from_brain' in opts and not g.quiet: - confirm_or_exit(cmessages['brain_warning'].format( - g.proj_name, *get_from_brain_opt_params(opts)), - "continue") - -for i in 'from_mnemonic','from_brain','from_seed','from_incog': - if infile or (i in opts): - seed = get_seed_retry(infile,opts) - if "from_incog" in opts or get_extension(infile) == g.incog_ext: - qmsg(cmessages['incog'] % make_chksum_8(seed)) - else: qmsg("") - break -else: - # Truncate random data for smaller seed lengths - seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8] - -salt = sha256(get_random(128,opts)).digest()[:g.salt_len] - -qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset']) - -passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts) - -key = make_key(passwd, salt, opts['hash_preset']) - -enc_seed = encrypt_seed(seed, key) - -write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts) +import mmgen.main +mmgen.main.main("walletgen") diff --git a/mmgen/Opts.py b/mmgen/Opts.py index 60ca02f7..178f825e 100755 --- a/mmgen/Opts.py +++ b/mmgen/Opts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -16,6 +16,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +""" +Opts.py: Option handling routines for the MMGen suite +""" + import sys import mmgen.config as g import mmgen.opt.Opts @@ -65,6 +69,9 @@ def parse_opts(argv,help_data): if v in opts: typeconvert_override_var(opts,v) else: opts[v] = eval("g."+v) + if "verbose" in opts: g.verbose = True + if "quiet" in opts: g.quiet = True + if g.debug: print "opts after typeconvert: %s" % opts return opts,args diff --git a/mmgen/__init__.py b/mmgen/__init__.py index 8fc4d095..d93c1b14 100755 --- a/mmgen/__init__.py +++ b/mmgen/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,10 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +MMGen = Multi-Mode GENerator, a Bitcoin cold storage/tracking solution for + the command line """ __all__ = [ 'rpc', diff --git a/mmgen/addr.py b/mmgen/addr.py index 2f133664..0ad95040 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -addr.py: Address generation/display routines for mmgen suite +addr.py: Address generation/display routines for the MMGen suite """ import sys @@ -68,8 +69,8 @@ def generate_addrs(seed, addrnums, opts): from subprocess import Popen, PIPE keyconv = "keyconv" - fmt = "num addr" if opts['gen_what'] == ("addrs") else ( - "num sec wif" if opts['gen_what'] == ("keys") else "num sec wif addr") + fmt = "num addr" if opts['gen_what'] == ["addrs"] else ( + "num sec wif" if opts['gen_what'] == ["keys"] else "num sec wif addr") from collections import namedtuple addrinfo = namedtuple("addrinfo",fmt) @@ -78,37 +79,31 @@ def generate_addrs(seed, addrnums, opts): t_addrs,num,pos,out = len(addrnums),0,0,[] addrnums.sort() # needed only if caller didn't sort - try: - while pos != t_addrs: - seed = sha512(seed).digest() - num += 1 # round + ws = 'key' if 'keys' in opts['gen_what'] else 'address' + if t_addrs != 1: wp = ws+"s" if ws == 'key' else ws+"es" - if g.debug: print "Seed round %s: %s" % (num, hexlify(seed)) - if num != addrnums[pos]: continue + while pos != t_addrs: + seed = sha512(seed).digest() + num += 1 # round - pos += 1 + if g.debug: print "Seed round %s: %s" % (num, hexlify(seed)) + if num != addrnums[pos]: continue - qmsg_r("\rGenerating %s %s (%s of %s)" % - (opts['gen_what'][-1],num,pos,t_addrs)) + pos += 1 - # Secret key is double sha256 of seed hash round /num/ - sec = sha256(sha256(seed).digest()).hexdigest() - wif = numtowif(int(sec,16)) + qmsg_r("\rGenerating %s #%s (%s of %s)" % (ws,num,pos,t_addrs)) - if 'addrs' in opts['gen_what']: addr = \ - Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \ - if keyconv else privnum2addr(int(sec,16)) + # Secret key is double sha256 of seed hash round /num/ + sec = sha256(sha256(seed).digest()).hexdigest() + wif = numtowif(int(sec,16)) - out.append(eval("addrinfo("+addrinfo_args+")")) + if 'addrs' in opts['gen_what']: addr = \ + Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \ + if keyconv else privnum2addr(int(sec,16)) - except KeyboardInterrupt: - msg("\nUser interrupt") - sys.exit(1) + out.append(eval("addrinfo("+addrinfo_args+")")) - w = 'key' if 'keys' in opts['gen_what'] else 'address' - if t_addrs != 1: w = w+"s" if w == 'key' else w+"es" - - qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15)) + qmsg("\rGenerated %s %s%s"%(t_addrs, wp, " "*15)) return out diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index 746eb6c5..38d084d7 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # MMGen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ bitcoin.py: Bitcoin address/key conversion functions """ diff --git a/mmgen/config.py b/mmgen/config.py index 6d01cb11..0a3ce065 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -config.py: Constants and configuration options for the mmgen suite +config.py: Constants and configuration options for the MMGen suite """ import sys, os diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 1f5968d5..509530e9 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -crypto.py: Cryptographic and related routines for the mmgen-tool utility +crypto.py: Cryptographic and related routines for the 'mmgen-tool' utility """ import sys @@ -368,27 +369,30 @@ def _get_seed_from_brain_passphrase(words,opts): # Vars for mmgen_*crypt functions only salt_len,sha256_len,nonce_len = 32,32,32 -def mmgen_encrypt(data,hash_preset,opts): +def mmgen_encrypt(data,what="data",hash_preset='3',opts={}): salt,iv,nonce = get_random(salt_len,opts),\ get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts) hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default") + vmsg("Encrypting %s" % what) qmsg("Using %s hash preset of '%s'" % (m,hp)) passwd = get_new_passphrase("passphrase",{}) key = make_key(passwd, salt, hp) enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key, - int(hexlify(iv),16)) + int(hexlify(iv),16), what=what) return salt+iv+enc_d -def mmgen_decrypt(data,hash_preset,opts): +def mmgen_decrypt(data,what="data",hash_preset='3',opts={}): dstart = salt_len + g.aesctr_iv_len salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:] hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default") + vmsg("Preparing to decrypt %s" % what) qmsg("Using %s hash preset of '%s'" % (m,hp)) passwd = get_mmgen_passphrase("Enter passphrase: ",{}) key = make_key(passwd, salt, hp) - dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16)) + dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what) if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest(): + vmsg("Success. Passphrase and hash preset are correct") return dec_d[sha256_len+nonce_len:] else: msg("Incorrect passphrase or hash preset") diff --git a/mmgen/license.py b/mmgen/license.py index 76cd0313..f52a4173 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ license.py: Show the license """ @@ -594,6 +595,7 @@ def do_license_msg(immed=False): prompt = "%s " % gpl['prompt'].strip() while True: + from mmgen.util import my_raw_input reply = get_char(prompt, immed_chars="wc" if immed else "") if reply == 'w': from mmgen.term import do_pager diff --git a/mmgen/main.py b/mmgen/main.py new file mode 100755 index 00000000..0713ec44 --- /dev/null +++ b/mmgen/main.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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 . + +""" +main.py - Script launcher for the MMGen suite +""" + +import sys, termios +from mmgen.util import msg + +def main(progname): + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + try: + if progname == "addrgen": import mmgen.main_addrgen + elif progname == "addrimport": import mmgen.main_addrimport + elif progname == "keygen": import mmgen.main_addrgen + elif progname == "passchg": import mmgen.main_passchg + elif progname == "pywallet": import mmgen.main_pywallet + elif progname == "tool": import mmgen.main_tool + elif progname == "txcreate": import mmgen.main_txcreate + elif progname == "txsend": import mmgen.main_txsend + elif progname == "txsign": import mmgen.main_txsign + elif progname == "walletchk": import mmgen.main_walletchk + elif progname == "walletgen": import mmgen.main_walletgen + except KeyboardInterrupt: + msg("\nUser interrupt") + termios.tcsetattr(fd, termios.TCSADRAIN, old) + sys.exit(1) diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py new file mode 100755 index 00000000..a6e82f9f --- /dev/null +++ b/mmgen/main_addrgen.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-addrgen: Generate a series or range of addresses from an MMGen + deterministic wallet +""" + +import sys + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.license import * +from mmgen.util import * +from mmgen.crypto import * +from mmgen.addr import * +from mmgen.tx import make_addr_data_chksum + +what = "keys" if sys.argv[0].split("-")[-1] == "keygen" else "addresses" + +help_data = { + 'prog_name': g.prog_name, + 'desc': """Generate a list or range of {} from an {g.proj_name} wallet, + mnemonic, seed or password""".format(what,g=g), + 'usage':"[opts] [infile]
", + 'options': """ +-h, --help Print this help message{} +-d, --outdir= d Specify an alternate directory 'd' for output +-c, --save-checksum Save address list checksum to file +-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry{} +-H, --show-hash-presets Show information on available hash presets +-K, --no-keyconv Use internal libraries for address generation + instead of 'keyconv' +-l, --seed-len= N Length of seed. Options: {seed_lens} + (default: {g.seed_len}) +-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when + hashing password (default: '{g.hash_preset}') +-P, --passwd-file= f Get passphrase from file 'f' +-q, --quiet Suppress warnings; overwrite files without + prompting +-S, --stdout Print {what} to stdout +-v, --verbose Produce more verbose output{} + +-b, --from-brain= l,p Generate {what} from a user-created password, + i.e. a "brainwallet", using seed length 'l' and + hash preset 'p' (comma-separated) +-g, --from-incog Generate {what} from an incognito wallet +-X, --from-incog-hex Generate {what} from incognito hexadecimal wallet +-G, --from-incog-hidden=f,o,l Generate {what} from incognito data in file + 'f' at offset 'o', with seed length of 'l' +-m, --from-mnemonic Generate {what} from an electrum-like mnemonic +-s, --from-seed Generate {what} from a seed in .{g.seed_ext} format +""".format( + *( + ( +"\n-A, --no-addresses Print only secret keys, no addresses", +"\n-f, --flat-list Produce a flat list of keys suitable for use with" + +"\n '{}-txsign'".format(g.proj_name.lower()), +"\n-x, --b16 Print secret keys in hexadecimal too" + ) + if what == "keys" else ("","","")), + seed_lens=", ".join([str(i) for i in g.seed_lens]), + what=what, g=g +), + 'notes': """ + +Addresses are given in a comma-separated list. Hyphen-separated ranges are +also allowed.{} + +If available, the external 'keyconv' program will be used for address +generation. + +Data for the --from- options will be taken from if +is specified. Otherwise, the user will be prompted to enter the data. + +For passphrases all combinations of whitespace are equal, and leading and +trailing space are ignored. This permits reading passphrase data from a +multi-line file with free spacing and indentation. This is particularly +convenient for long brainwallet passphrases, for example. + +BRAINWALLET NOTE: + +As brainwallets require especially strong hashing to thwart dictionary +attacks, the brainwallet hash preset must be specified by the user, using +the 'p' parameter of the '--from-brain' option + +The '--from-brain' option also requires the user to specify a seed length +(the 'l' parameter) + +For a brainwallet passphrase to always generate the same keys and addresses, +the same 'l' and 'p' parameters to '--from-brain' must be used in all future +invocations with that passphrase +""".format("\n\nBy default, both addresses and secret keys are generated." + if what == "keys" else "") +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if 'show_hash_presets' in opts: show_hash_presets() +if 'from_incog_hex' in opts or 'from_incog_hidden' in opts: + opts['from_incog'] = True + +if g.debug: show_opts_and_cmd_args(opts,cmd_args) + +if len(cmd_args) == 1 and ( + 'from_mnemonic' in opts + or 'from_brain' in opts + or 'from_seed' in opts + or 'from_incog_hidden' in opts + ): + infile,addr_idx_arg = "",cmd_args[0] +elif len(cmd_args) == 2: + infile,addr_idx_arg = cmd_args + check_infile(infile) +else: usage(help_data) + +addr_idxs = parse_address_list(addr_idx_arg) + +if not addr_idxs: sys.exit(2) + +do_license_msg() + +# Interact with user: +if what == "keys" and not g.quiet: + confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue') + +# Generate data: + +seed = get_seed_retry(infile,opts) +seed_id = make_chksum_8(seed) + +for l in ( + ('flat_list', 'no_addresses'), + ('flat_list', 'b16'), +): warn_incompatible_opts(opts,l) + +opts['gen_what'] = \ + ["addrs"] if what == "addresses" else ( + ["keys"] if 'no_addresses' in opts else ["addrs","keys"]) +addr_data = generate_addrs(seed, addr_idxs, opts) +addr_data_chksum = make_addr_data_chksum([(a.num,a.addr) + for a in addr_data]) if 'addrs' in opts['gen_what'] else "" +addr_data_str = format_addr_data( + addr_data, addr_data_chksum, seed_id, addr_idxs, opts) + +outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs)) + +if 'flat_list' in opts and user_confirm("Encrypt key list?"): + hp = get_hash_preset_from_user('3') + addr_data_str = mmgen_encrypt(addr_data_str,"key list",hp,opts) + enc_ext = "." + g.mmenc_ext +else: enc_ext = "" + +# Output data: +if 'stdout' in opts or not sys.stdout.isatty(): + if enc_ext and sys.stdout.isatty(): + msg("Cannot write encrypted data to screen. Exiting") + sys.exit(2) + c = True if (what == "keys" and not g.quiet and sys.stdout.isatty()) else False + write_to_stdout(addr_data_str,what,c) +else: + confirm_overwrite = False if g.quiet else True + outfile = "%s.%s%s" % (outfile_base, ( + g.keylist_ext if 'flat_list' in opts else ( + g.keyfile_ext if opts['gen_what'] == ("keys") else ( + g.addrfile_ext if opts['gen_what'] == ("addrs") else "akeys"))), enc_ext) + write_to_file(outfile,addr_data_str,opts,what,confirm_overwrite,True) + +if 'addrs' in opts['gen_what']: + msg("Checksum for address data {}: {}".format(outfile_base,addr_data_chksum)) + if 'save_checksum' in opts: + a = "address data checksum" + write_to_file(outfile_base+".chk",addr_data_chksum,opts,a,False,True) + else: + qmsg("This checksum will be used to verify the address file in the future.") + qmsg("Record it to a safe location.") diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py new file mode 100755 index 00000000..f39e50d8 --- /dev/null +++ b/mmgen/main_addrimport.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-addrimport: Import addresses into a MMGen bitcoind watching wallet +""" + +import sys +from mmgen.Opts import * +from mmgen.license import * +from mmgen.util import * +from mmgen.tx import connect_to_bitcoind,parse_addrs_file + +help_data = { + 'prog_name': g.prog_name, + 'desc': """Import addresses (both {pnm} and non-{pnm}) into a bitcoind + watching wallet""".format(pnm=g.proj_name), + 'usage':"[opts] [mmgen address file]", + 'options': """ +-h, --help Print this help message +-l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f' +-q, --quiet Suppress warnings +-r, --rescan Rescan the blockchain. Required if address to import is + on the blockchain and has a balance. Rescanning is slow. +""" +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if len(cmd_args) != 1 and not 'addrlist' in opts: + msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)") + sys.exit(1) + +if cmd_args: + check_infile(cmd_args[0]) + seed_id,addr_data = parse_addrs_file(cmd_args[0]) +else: + seed_id,addr_data = "",[] + +if 'addrlist' in opts: + lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses", + trim_comments=True) + addr_data += [(None,l) for l in lines] + +from mmgen.bitcoin import verify_addr +qmsg_r("Validating addresses...") +for i in addr_data: + if not verify_addr(i[1],verbose=True): + msg("%s: invalid address" % i) + sys.exit(2) +qmsg("OK") + +import mmgen.config as g +g.http_timeout = 3600 + +c = connect_to_bitcoind() + +m = """ +WARNING: You've chosen the '--rescan' option. Rescanning the block chain is +necessary only if an address you're importing is already on the block chain +and has a balance. Note that the rescanning process is very slow (>30 min. +for each imported address on a low-powered computer). + """.strip() if "rescan" in opts else """ +WARNING: If any of the addresses you're importing is already on the block chain +and has a balance, you must exit the program now and rerun it using the +'--rescan' option. Otherwise you may ignore this message and continue. +""".strip() + +if g.quiet: m = "" +confirm_or_exit(m, "continue", expect="YES") + +err_flag = False + +def import_address(addr,label,rescan): + try: + c.importaddress(addr,label,rescan) + except: + global err_flag + err_flag = True + + +w1 = len(str(len(addr_data))) * 2 + 2 +w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12 + +if "rescan" in opts: + import threading + import time + msg_fmt = "\r%s %-" + str(w1) + "s %-34s %-" + str(w2) + "s" +else: + msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s" + +msg("Importing addresses") +for n,i in enumerate(addr_data): + if i[0]: + comment = " " + i[2] if len(i) == 3 else "" + label = "%s:%s%s" % (seed_id,i[0],comment) + else: label = "non-mmgen" + + if "rescan" in opts: + t = threading.Thread(target=import_address, args=(i[1],label,True)) + t.daemon = True + t.start() + + start = int(time.time()) + + while True: + if t.is_alive(): + elapsed = int(time.time() - start) + msg_r(msg_fmt % ( + secs_to_hms(elapsed), + ("%s/%s:" % (n+1,len(addr_data))), + i[1], "(" + label + ")" + ) + ) + time.sleep(1) + else: + if err_flag: msg("\nImport failed"); sys.exit(2) + msg("\nOK") + break + else: + import_address(i[1],label,rescan=False) + msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))), + i[1], "(" + label + ")")) + if err_flag: msg("\nImport failed"); sys.exit(2) + msg(" - OK") diff --git a/mmgen/main_passchg.py b/mmgen/main_passchg.py new file mode 100755 index 00000000..906057a4 --- /dev/null +++ b/mmgen/main_passchg.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-passchg: Change an MMGen deterministic wallet's passphrase, label or + hash preset +""" + +import sys +from mmgen.Opts import * +from mmgen.util import * +from mmgen.crypto import * +import mmgen.config as g + +help_data = { + 'prog_name': g.prog_name, + 'desc': """Change the passphrase, hash preset or label of an {} + deterministic wallet""".format(g.proj_name), + 'usage': "[opts] [filename]", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-H, --show-hash-presets Show information on available hash presets +-k, --keep-old-passphrase Keep old passphrase (use when changing hash + strength or label only) +-L, --label= l Change the wallet's label to 'l' +-p, --hash-preset= p Change scrypt.hash() parameters to preset 'p' + (default: '{g.hash_preset}') +-P, --passwd-file= f Get new passphrase from file 'f' +-r, --usr-randchars= n Get 'n' characters of additional randomness from + user (min={g.min_urandchars}, max={g.max_urandchars}) +-q, --quiet Suppress warnings; overwrite files without + prompting +-v, --verbose Produce more verbose output +""".format(g=g), + 'notes': """ + +NOTE: The key ID will change if either the passphrase or hash preset are + changed +""" +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if 'show_hash_presets' in opts: show_hash_presets() + +if len(cmd_args) != 1: + msg("One input file must be specified") + sys.exit(2) +infile = cmd_args[0] + +# Old key: +label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile) +seed_id,key_id = metadata[:2] + +# Repeat on incorrect pw entry +prompt = "Enter %spassphrase: " % ("" + if 'keep_old_passphrase' in opts else "old ") +while True: + passwd = get_mmgen_passphrase(prompt,{}) + key = make_key(passwd, salt, hash_preset) + seed = decrypt_seed(enc_seed, key, seed_id, key_id) + if seed: break + +changed = {} + +if 'label' in opts: + if opts['label'] != label: + msg("Label changed: '%s' -> '%s'" % (label, opts['label'])) + changed['label'] = True + else: + msg("Label is unchanged: '%s'" % (label)) +else: opts['label'] = label # Copy the old label + +if 'hash_preset' in opts: + if hash_preset != opts['hash_preset']: + qmsg("Hash preset has changed (%s -> %s)" % + (hash_preset, opts['hash_preset'])) + changed['preset'] = True + else: + msg("Hash preset is unchanged") +else: + opts['hash_preset'] = hash_preset + +if 'keep_old_passphrase' in opts: + msg("Keeping old passphrase by user request") +else: + new_passwd = get_new_passphrase("new passphrase", opts) + + if new_passwd == passwd: + qmsg("Passphrase is unchanged") + else: + qmsg("Passphrase has changed") + passwd = new_passwd + changed['passwd'] = True + +if 'preset' in changed or 'passwd' in changed: # Update key ID, salt + qmsg("Will update salt and key ID") + + from hashlib import sha256 + + salt = sha256(salt + get_random(128,opts)).digest()[:g.salt_len] + key = make_key(passwd, salt, opts['hash_preset']) + new_key_id = make_chksum_8(key) + qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id)) + key_id = new_key_id + enc_seed = encrypt_seed(seed, key) +elif not 'label' in changed: + msg("Data unchanged. No file will be written") + sys.exit(2) + +write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts) diff --git a/mmgen/main_pywallet.py b/mmgen/main_pywallet.py new file mode 100755 index 00000000..086ccebe --- /dev/null +++ b/mmgen/main_pywallet.py @@ -0,0 +1,1677 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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 +# dump keys, addresses or keys for specified addresses (output in flat list) + +# PyWallet 1.2.1 (Public Domain) +# http://github.com/joric/pywallet +# Most of the actual PyWallet code placed in the public domain. +# PyWallet includes portions of free software, listed below. + +# BitcoinTools (wallet.dat handling code, MIT License) +# https://github.com/gavinandresen/bitcointools +# Copyright (c) 2010 Gavin Andresen + +# python-ecdsa (EC_KEY implementation, MIT License) +# http://github.com/warner/python-ecdsa +# "python-ecdsa" Copyright (c) 2010 Brian Warner +# Portions written in 2005 by Peter Pearson and placed in the public domain. + +# SlowAES (aes.py code, Apache 2 License) +# http://code.google.com/p/slowaes/ +# Copyright (c) 2008, Josh Davis (http://www.josh-davis.org), +# Alex Martelli (http://www.aleax.it) +# Ported from C code written by Laurent Haan (http://www.progressive-coding.com) + +from bsddb.db import * +import sys, time +import json +import logging +import struct +import StringIO +import traceback +import socket +import types +import string +import exceptions +import hashlib +import random +import math + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.util import msg + +max_version = 60000 +addrtype = 0 +json_db = {} +private_keys = [] +password = None + +help_data = { + 'prog_name': g.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 +-e, --echo-passphrase Display passphrase on screen upon entry +-j, --json Dump wallet in json format +-k, --keys Dump all private keys (flat list) +-a, --addrs Dump all addresses (flat list) +-K, --keysforaddrs= f Dump private keys for addresses listed in file 'f' +-P, --passwd-file= f Get passphrase from file 'f' +-S, --stdout Dump to stdout rather than file +""" +} + +opts,cmd_args = parse_opts(sys.argv,help_data) +from mmgen.Opts import warn_incompatible_opts +warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs')) + +if len(cmd_args) == 1: + from mmgen.util import check_infile + check_infile(cmd_args[0]) +else: + usage(help_data) + +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) + +def append_PKCS7_padding(s): + """return s padded to a multiple of 16-bytes by PKCS7 padding""" + numpads = 16 - (len(s)%16) + return s + numpads*chr(numpads) + +def strip_PKCS7_padding(s): + """return s stripped of PKCS7 padding""" + if len(s)%16 or not s: + raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) + numpads = ord(s[-1]) + if numpads > 16: + raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) + return s[:-numpads] + +class AES(object): + # valid key sizes + keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) + + # Rijndael S-box + sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, + 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, + 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, + 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, + 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, + 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, + 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, + 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, + 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, + 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, + 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, + 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, + 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, + 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, + 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, + 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, + 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, + 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, + 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, + 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, + 0x54, 0xbb, 0x16] + + # Rijndael Inverted S-box + rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, + 0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, + 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54, + 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, + 0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, + 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8, + 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, + 0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, + 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab, + 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, + 0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, + 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, + 0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, + 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d, + 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b , + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, + 0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, + 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60, + 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, + 0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, + 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b, + 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, + 0x21, 0x0c, 0x7d] + + def getSBoxValue(self,num): + """Retrieves a given S-Box Value""" + return self.sbox[num] + + def getSBoxInvert(self,num): + """Retrieves a given Inverted S-Box Value""" + return self.rsbox[num] + + def rotate(self, word): + """ Rijndael's key schedule rotate operation. + + Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d + Word is an char list of size 4 (32 bits overall). + """ + return word[1:] + word[:1] + + # Rijndael Rcon + Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, + 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, + 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, + 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, + 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, + 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, + 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, + 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, + 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, + 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, + 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, + 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, + 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, + 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, + 0xe8, 0xcb ] + + def getRconValue(self, num): + """Retrieves a given Rcon Value""" + return self.Rcon[num] + + def core(self, word, iteration): + """Key schedule core.""" + # rotate the 32-bit word 8 bits to the left + word = self.rotate(word) + # apply S-Box substitution on all 4 parts of the 32-bit word + for i in range(4): + word[i] = self.getSBoxValue(word[i]) + # XOR the output of the rcon operation with i to the first part + # (leftmost) only + word[0] = word[0] ^ self.getRconValue(iteration) + return word + + def expandKey(self, key, size, expandedKeySize): + """Rijndael's key expansion. + + Expands an 128,192,256 key into an 176,208,240 bytes key + + expandedKey is a char list of large enough size, + key is the non-expanded key. + """ + # current expanded keySize, in bytes + currentSize = 0 + rconIteration = 1 + expandedKey = [0] * expandedKeySize + + # set the 16, 24, 32 bytes of the expanded key to the input key + for j in range(size): + expandedKey[j] = key[j] + currentSize += size + + while currentSize < expandedKeySize: + # assign the previous 4 bytes to the temporary value t + t = expandedKey[currentSize-4:currentSize] + + # every 16,24,32 bytes we apply the core schedule to t + # and increment rconIteration afterwards + if currentSize % size == 0: + t = self.core(t, rconIteration) + rconIteration += 1 + # For 256-bit keys, we add an extra sbox to the calculation + if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): + for l in range(4): t[l] = self.getSBoxValue(t[l]) + + # We XOR t with the four-byte block 16,24,32 bytes before the new + # expanded key. This becomes the next four bytes in the expanded + # key. + for m in range(4): + expandedKey[currentSize] = expandedKey[currentSize - size] ^ \ + t[m] + currentSize += 1 + + return expandedKey + + def addRoundKey(self, state, roundKey): + """Adds (XORs) the round key to the state.""" + for i in range(16): + state[i] ^= roundKey[i] + return state + + def createRoundKey(self, expandedKey, roundKeyPointer): + """Create a round key. + Creates a round key from the given expanded key and the + position within the expanded key. + """ + roundKey = [0] * 16 + for i in range(4): + for j in range(4): + roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j] + return roundKey + + def galois_multiplication(self, a, b): + """Galois multiplication of 8 bit characters a and b.""" + p = 0 + for counter in range(8): + if b & 1: p ^= a + hi_bit_set = a & 0x80 + a <<= 1 + # keep a 8 bit + a &= 0xFF + if hi_bit_set: + a ^= 0x1b + b >>= 1 + return p + + # + # substitute all the values from the state with the value in the SBox + # using the state value as index for the SBox + # + def subBytes(self, state, isInv): + if isInv: getter = self.getSBoxInvert + else: getter = self.getSBoxValue + for i in range(16): state[i] = getter(state[i]) + return state + + # iterate over the 4 rows and call shiftRow() with that row + def shiftRows(self, state, isInv): + for i in range(4): + state = self.shiftRow(state, i*4, i, isInv) + return state + + # each iteration shifts the row to the left by 1 + def shiftRow(self, state, statePointer, nbr, isInv): + for i in range(nbr): + if isInv: + state[statePointer:statePointer+4] = \ + state[statePointer+3:statePointer+4] + \ + state[statePointer:statePointer+3] + else: + state[statePointer:statePointer+4] = \ + state[statePointer+1:statePointer+4] + \ + state[statePointer:statePointer+1] + return state + + # galois multiplication of the 4x4 matrix + def mixColumns(self, state, isInv): + # iterate over the 4 columns + for i in range(4): + # construct one column by slicing over the 4 rows + column = state[i:i+16:4] + # apply the mixColumn on one column + column = self.mixColumn(column, isInv) + # put the values back into the state + state[i:i+16:4] = column + + return state + + # galois multiplication of 1 column of the 4x4 matrix + def mixColumn(self, column, isInv): + if isInv: mult = [14, 9, 13, 11] + else: mult = [2, 1, 1, 3] + cpy = list(column) + g = self.galois_multiplication + + column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ + g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) + column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ + g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) + column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ + g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) + column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ + g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) + return column + + # applies the 4 operations of the forward round in sequence + def aes_round(self, state, roundKey): + state = self.subBytes(state, False) + state = self.shiftRows(state, False) + state = self.mixColumns(state, False) + state = self.addRoundKey(state, roundKey) + return state + + # applies the 4 operations of the inverse round in sequence + def aes_invRound(self, state, roundKey): + state = self.shiftRows(state, True) + state = self.subBytes(state, True) + state = self.addRoundKey(state, roundKey) + state = self.mixColumns(state, True) + return state + + # Perform the initial operations, the standard round, and the final + # operations of the forward aes, creating a round key for each round + def aes_main(self, state, expandedKey, nbrRounds): + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) + i = 1 + while i < nbrRounds: + state = self.aes_round(state, self.createRoundKey(expandedKey, 16*i)) + i += 1 + state = self.subBytes(state, False) + state = self.shiftRows(state, False) + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) + return state + + # Perform the initial operations, the standard round, and the final + # operations of the inverse aes, creating a round key for each round + def aes_invMain(self, state, expandedKey, nbrRounds): + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16*nbrRounds)) + i = nbrRounds - 1 + while i > 0: + state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16*i)) + i -= 1 + state = self.shiftRows(state, True) + state = self.subBytes(state, True) + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) + return state + + # encrypts a 128 bit input block against the given key of size specified + def encrypt(self, iput, key, size): + output = [0] * 16 + # the number of rounds + nbrRounds = 0 + # the 128 bit block to encode + block = [0] * 16 + # set the number of rounds + if size == self.keySize["SIZE_128"]: nbrRounds = 10 + elif size == self.keySize["SIZE_192"]: nbrRounds = 12 + elif size == self.keySize["SIZE_256"]: nbrRounds = 14 + else: return None + + # the expanded keySize + expandedKeySize = 16*(nbrRounds+1) + + # Set the block values, for the block: + # a0,0 a0,1 a0,2 a0,3 + # a1,0 a1,1 a1,2 a1,3 + # a2,0 a2,1 a2,2 a2,3 + # a3,0 a3,1 a3,2 a3,3 + # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 + # + # iterate over the columns + for i in range(4): + # iterate over the rows + for j in range(4): + block[(i+(j*4))] = iput[(i*4)+j] + + # expand the key into an 176, 208, 240 bytes key + # the expanded key + expandedKey = self.expandKey(key, size, expandedKeySize) + + # encrypt the block using the expandedKey + block = self.aes_main(block, expandedKey, nbrRounds) + + # unmap the block again into the output + for k in range(4): + # iterate over the rows + for l in range(4): + output[(k*4)+l] = block[(k+(l*4))] + return output + + # decrypts a 128 bit input block against the given key of size specified + def decrypt(self, iput, key, size): + output = [0] * 16 + # the number of rounds + nbrRounds = 0 + # the 128 bit block to decode + block = [0] * 16 + # set the number of rounds + if size == self.keySize["SIZE_128"]: nbrRounds = 10 + elif size == self.keySize["SIZE_192"]: nbrRounds = 12 + elif size == self.keySize["SIZE_256"]: nbrRounds = 14 + else: return None + + # the expanded keySize + expandedKeySize = 16*(nbrRounds+1) + + # Set the block values, for the block: + # a0,0 a0,1 a0,2 a0,3 + # a1,0 a1,1 a1,2 a1,3 + # a2,0 a2,1 a2,2 a2,3 + # a3,0 a3,1 a3,2 a3,3 + # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 + + # iterate over the columns + for i in range(4): + # iterate over the rows + for j in range(4): + block[(i+(j*4))] = iput[(i*4)+j] + # expand the key into an 176, 208, 240 bytes key + expandedKey = self.expandKey(key, size, expandedKeySize) + # decrypt the block using the expandedKey + block = self.aes_invMain(block, expandedKey, nbrRounds) + # unmap the block again into the output + for k in range(4): + # iterate over the rows + for l in range(4): + output[(k*4)+l] = block[(k+(l*4))] + return output + +class AESModeOfOperation(object): + + aes = AES() + + # structure of supported modes of operation + modeOfOperation = dict(OFB=0, CFB=1, CBC=2) + + # converts a 16 character string into a number array + def convertString(self, string, start, end, mode): + if end - start > 16: end = start + 16 + if mode == self.modeOfOperation["CBC"]: ar = [0] * 16 + else: ar = [] + + i = start + j = 0 + while len(ar) < end - start: + ar.append(0) + while i < end: + ar[j] = ord(string[i]) + j += 1 + i += 1 + return ar + + # Mode of Operation Encryption + # stringIn - Input String + # mode - mode of type modeOfOperation + # hexKey - a hex key of the bit length size + # size - the bit length of the key + # hexIV - the 128 bit hex Initilization Vector + def encrypt(self, stringIn, mode, key, size, IV): + if len(key) % size: + return None + if len(IV) % 16: + return None + # the AES input/output + plaintext = [] + iput = [0] * 16 + output = [] + ciphertext = [0] * 16 + # the output cipher string + cipherOut = [] + # char firstRound + firstRound = True + if stringIn != None: + for j in range(int(math.ceil(float(len(stringIn))/16))): + start = j*16 + end = j*16+16 + if end > len(stringIn): + end = len(stringIn) + plaintext = self.convertString(stringIn, start, end, mode) + # print 'PT@%s:%s' % (j, plaintext) + if mode == self.modeOfOperation["CFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(plaintext)-1 < i: + ciphertext[i] = 0 ^ output[i] + elif len(output)-1 < i: + ciphertext[i] = plaintext[i] ^ 0 + elif len(plaintext)-1 < i and len(output) < i: + ciphertext[i] = 0 ^ 0 + else: + ciphertext[i] = plaintext[i] ^ output[i] + for k in range(end-start): + cipherOut.append(ciphertext[k]) + iput = ciphertext + elif mode == self.modeOfOperation["OFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(plaintext)-1 < i: + ciphertext[i] = 0 ^ output[i] + elif len(output)-1 < i: + ciphertext[i] = plaintext[i] ^ 0 + elif len(plaintext)-1 < i and len(output) < i: + ciphertext[i] = 0 ^ 0 + else: + ciphertext[i] = plaintext[i] ^ output[i] + for k in range(end-start): + cipherOut.append(ciphertext[k]) + iput = output + elif mode == self.modeOfOperation["CBC"]: + for i in range(16): + if firstRound: + iput[i] = plaintext[i] ^ IV[i] + else: + iput[i] = plaintext[i] ^ ciphertext[i] + # print 'IP@%s:%s' % (j, iput) + firstRound = False + ciphertext = self.aes.encrypt(iput, key, size) + # always 16 bytes because of the padding for CBC + for k in range(16): + cipherOut.append(ciphertext[k]) + return mode, len(stringIn), cipherOut + + # Mode of Operation Decryption + # cipherIn - Encrypted String + # originalsize - The unencrypted string length - required for CBC + # mode - mode of type modeOfOperation + # key - a number array of the bit length size + # size - the bit length of the key + # IV - the 128 bit number array Initilization Vector + def decrypt(self, cipherIn, originalsize, mode, key, size, IV): + # cipherIn = unescCtrlChars(cipherIn) + if len(key) % size: + return None + if len(IV) % 16: + return None + # the AES input/output + ciphertext = [] + iput = [] + output = [] + plaintext = [0] * 16 + # the output plain text string + stringOut = '' + # char firstRound + firstRound = True + if cipherIn != None: + for j in range(int(math.ceil(float(len(cipherIn))/16))): + start = j*16 + end = j*16+16 + if j*16+16 > len(cipherIn): + end = len(cipherIn) + ciphertext = cipherIn[start:end] + if mode == self.modeOfOperation["CFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(output)-1 < i: + plaintext[i] = 0 ^ ciphertext[i] + elif len(ciphertext)-1 < i: + plaintext[i] = output[i] ^ 0 + elif len(output)-1 < i and len(ciphertext) < i: + plaintext[i] = 0 ^ 0 + else: + plaintext[i] = output[i] ^ ciphertext[i] + for k in range(end-start): + stringOut += chr(plaintext[k]) + iput = ciphertext + elif mode == self.modeOfOperation["OFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(output)-1 < i: + plaintext[i] = 0 ^ ciphertext[i] + elif len(ciphertext)-1 < i: + plaintext[i] = output[i] ^ 0 + elif len(output)-1 < i and len(ciphertext) < i: + plaintext[i] = 0 ^ 0 + else: + plaintext[i] = output[i] ^ ciphertext[i] + for k in range(end-start): + stringOut += chr(plaintext[k]) + iput = output + elif mode == self.modeOfOperation["CBC"]: + output = self.aes.decrypt(ciphertext, key, size) + for i in range(16): + if firstRound: + plaintext[i] = IV[i] ^ output[i] + else: + plaintext[i] = iput[i] ^ output[i] + firstRound = False + if originalsize is not None and originalsize < end: + for k in range(originalsize-start): + stringOut += chr(plaintext[k]) + else: + for k in range(end-start): + stringOut += chr(plaintext[k]) + iput = ciphertext + return stringOut + +# end of aes.py code + +# pywallet crypter implementation + +crypter = None + +try: + from Crypto.Cipher import AES + crypter = 'pycrypto' +except: + pass + +class Crypter_pycrypto( object ): + def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): + if nDerivationMethod != 0: + return 0 + data = vKeyData + vSalt + for i in xrange(nDerivIterations): + data = hashlib.sha512(data).digest() + self.SetKey(data[0:32]) + self.SetIV(data[32:32+16]) + return len(data) + + def SetKey(self, key): + self.chKey = key + + def SetIV(self, iv): + self.chIV = iv[0:16] + + def Encrypt(self, data): + return AES.new(self.chKey,AES.MODE_CBC,self.chIV).encrypt(data)[0:32] + + def Decrypt(self, data): + return AES.new(self.chKey,AES.MODE_CBC,self.chIV).decrypt(data)[0:32] + +try: + if not crypter: + import ctypes + import ctypes.util + ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32') + crypter = 'ssl' +except: + pass + +class Crypter_ssl(object): + def __init__(self): + self.chKey = ctypes.create_string_buffer (32) + self.chIV = ctypes.create_string_buffer (16) + + def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): + if nDerivationMethod != 0: + return 0 + strKeyData = ctypes.create_string_buffer (vKeyData) + chSalt = ctypes.create_string_buffer (vSalt) + return ssl.EVP_BytesToKey(ssl.EVP_aes_256_cbc(), ssl.EVP_sha512(), chSalt, strKeyData, + len(vKeyData), nDerivIterations, ctypes.byref(self.chKey), ctypes.byref(self.chIV)) + + def SetKey(self, key): + self.chKey = ctypes.create_string_buffer(key) + + def SetIV(self, iv): + self.chIV = ctypes.create_string_buffer(iv) + + def Encrypt(self, data): + buf = ctypes.create_string_buffer(len(data) + 16) + written = ctypes.c_int(0) + final = ctypes.c_int(0) + ctx = ssl.EVP_CIPHER_CTX_new() + ssl.EVP_CIPHER_CTX_init(ctx) + ssl.EVP_EncryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) + ssl.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) + output = buf.raw[:written.value] + ssl.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)) + output += buf.raw[:final.value] + return output + + def Decrypt(self, data): + buf = ctypes.create_string_buffer(len(data) + 16) + written = ctypes.c_int(0) + final = ctypes.c_int(0) + ctx = ssl.EVP_CIPHER_CTX_new() + ssl.EVP_CIPHER_CTX_init(ctx) + ssl.EVP_DecryptInit_ex(ctx, ssl.EVP_aes_256_cbc(), None, self.chKey, self.chIV) + ssl.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)) + output = buf.raw[:written.value] + ssl.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)) + output += buf.raw[:final.value] + return output + +class Crypter_pure(object): + def __init__(self): + self.m = AESModeOfOperation() + self.cbc = self.m.modeOfOperation["CBC"] + self.sz = self.m.aes.keySize["SIZE_256"] + + def SetKeyFromPassphrase(self, vKeyData, vSalt, nDerivIterations, nDerivationMethod): + if nDerivationMethod != 0: + return 0 + data = vKeyData + vSalt + for i in xrange(nDerivIterations): + data = hashlib.sha512(data).digest() + self.SetKey(data[0:32]) + self.SetIV(data[32:32+16]) + return len(data) + + def SetKey(self, key): + self.chKey = [ord(i) for i in key] + + def SetIV(self, iv): + self.chIV = [ord(i) for i in iv] + + def Encrypt(self, data): + mode, size, cypher = self.m.encrypt(data, self.cbc, self.chKey, self.sz, self.chIV) + return ''.join(map(chr, cypher)) + + def Decrypt(self, data): + chData = [ord(i) for i in data] + return self.m.decrypt(chData, self.sz, self.cbc, self.chKey, self.sz, self.chIV) + +# secp256k1 + +_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL +_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L +_b = 0x0000000000000000000000000000000000000000000000000000000000000007L +_a = 0x0000000000000000000000000000000000000000000000000000000000000000L +_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L +_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L + +# python-ecdsa code (EC_KEY implementation) + +class CurveFp( object ): + def __init__( self, p, a, b ): + self.__p = p + self.__a = a + self.__b = b + + def p( self ): + return self.__p + + def a( self ): + return self.__a + + def b( self ): + return self.__b + + def contains_point( self, x, y ): + return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 + +class Point( object ): + def __init__( self, curve, x, y, order = None ): + self.__curve = curve + self.__x = x + self.__y = y + self.__order = order + if self.__curve: assert self.__curve.contains_point( x, y ) + if order: assert self * order == INFINITY + + def __add__( self, other ): + if other == INFINITY: return self + if self == INFINITY: return other + assert self.__curve == other.__curve + if self.__x == other.__x: + if ( self.__y + other.__y ) % self.__curve.p() == 0: + return INFINITY + else: + return self.double() + + p = self.__curve.p() + l = ( ( other.__y - self.__y ) * \ + inverse_mod( other.__x - self.__x, p ) ) % p + x3 = ( l * l - self.__x - other.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + return Point( self.__curve, x3, y3 ) + + def __mul__( self, other ): + def leftmost_bit( x ): + assert x > 0 + result = 1L + while result <= x: result = 2 * result + return result / 2 + + e = other + if self.__order: e = e % self.__order + if e == 0: return INFINITY + if self == INFINITY: return INFINITY + assert e > 0 + e3 = 3 * e + negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) + i = leftmost_bit( e3 ) / 2 + result = self + while i > 1: + result = result.double() + if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self + if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self + i = i / 2 + return result + + def __rmul__( self, other ): + return self * other + + def __str__( self ): + if self == INFINITY: return "infinity" + return "(%d,%d)" % ( self.__x, self.__y ) + + def double( self ): + if self == INFINITY: + return INFINITY + + p = self.__curve.p() + a = self.__curve.a() + l = ( ( 3 * self.__x * self.__x + a ) * \ + inverse_mod( 2 * self.__y, p ) ) % p + x3 = ( l * l - 2 * self.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + return Point( self.__curve, x3, y3 ) + + def x( self ): + return self.__x + + def y( self ): + return self.__y + + def curve( self ): + return self.__curve + + def order( self ): + return self.__order + +INFINITY = Point( None, None, None ) + +def inverse_mod( a, m ): + if a < 0 or m <= a: a = a % m + c, d = a, m + uc, vc, ud, vd = 1, 0, 0, 1 + while c != 0: + q, c, d = divmod( d, c ) + ( c, ) + uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc + assert d == 1 + if ud > 0: return ud + else: return ud + m + +class Signature( object ): + def __init__( self, r, s ): + self.r = r + self.s = s + +class Public_key( object ): + def __init__( self, generator, point ): + self.curve = generator.curve() + self.generator = generator + self.point = point + n = generator.order() + if not n: + raise RuntimeError, "Generator point must have order." + if not n * point == INFINITY: + raise RuntimeError, "Generator point order is bad." + if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): + raise RuntimeError, "Generator point has x or y out of range." + + def verifies( self, hash, signature ): + G = self.generator + n = G.order() + r = signature.r + s = signature.s + if r < 1 or r > n-1: return False + if s < 1 or s > n-1: return False + c = inverse_mod( s, n ) + u1 = ( hash * c ) % n + u2 = ( r * c ) % n + xy = u1 * G + u2 * self.point + v = xy.x() % n + return v == r + +class Private_key( object ): + def __init__( self, public_key, secret_multiplier ): + self.public_key = public_key + self.secret_multiplier = secret_multiplier + + def der( self ): + hex_der_key = '06052b8104000a30740201010420' + \ + '%064x' % self.secret_multiplier + \ + 'a00706052b8104000aa14403420004' + \ + '%064x' % self.public_key.point.x() + \ + '%064x' % self.public_key.point.y() + return hex_der_key.decode('hex') + + def sign( self, hash, random_k ): + G = self.public_key.generator + n = G.order() + k = random_k % n + p1 = k * G + r = p1.x() + if r == 0: raise RuntimeError, "amazingly unlucky random number r" + s = ( inverse_mod( k, n ) * \ + ( hash + ( self.secret_multiplier * r ) % n ) ) % n + if s == 0: raise RuntimeError, "amazingly unlucky random number s" + return Signature( r, s ) + +class EC_KEY(object): + def __init__( self, secret ): + curve = CurveFp( _p, _a, _b ) + generator = Point( curve, _Gx, _Gy, _r ) + self.pubkey = Public_key( generator, generator * secret ) + self.privkey = Private_key( self.pubkey, secret ) + self.secret = secret + +# end of python-ecdsa code + +# pywallet openssl private key implementation + +def i2d_ECPrivateKey(pkey, compressed=False): + if compressed: + key = '3081d30201010420' + \ + '%064x' % pkey.secret + \ + 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ + '%064x' % _p + \ + '3006040100040107042102' + \ + '%064x' % _Gx + \ + '022100' + \ + '%064x' % _r + \ + '020101a124032200' + else: + key = '308201130201010420' + \ + '%064x' % pkey.secret + \ + 'a081a53081a2020101302c06072a8648ce3d0101022100' + \ + '%064x' % _p + \ + '3006040100040107044104' + \ + '%064x' % _Gx + \ + '%064x' % _Gy + \ + '022100' + \ + '%064x' % _r + \ + '020101a144034200' + + return key.decode('hex') + i2o_ECPublicKey(pkey, compressed) + +def i2o_ECPublicKey(pkey, compressed=False): + # public keys are 65 bytes long (520 bits) + # 0x04 + 32-byte X-coordinate + 32-byte Y-coordinate + # 0x00 = point at infinity, 0x02 and 0x03 = compressed, 0x04 = uncompressed + # compressed keys: where is 0x02 if y is even and 0x03 if y is odd + if compressed: + if pkey.pubkey.point.y() & 1: + key = '03' + '%064x' % pkey.pubkey.point.x() + else: + key = '02' + '%064x' % pkey.pubkey.point.x() + else: + key = '04' + \ + '%064x' % pkey.pubkey.point.x() + \ + '%064x' % pkey.pubkey.point.y() + + return key.decode('hex') + +# bitcointools hashes and base58 implementation + +def hash_160(public_key): + md = hashlib.new('ripemd160') + md.update(hashlib.sha256(public_key).digest()) + return md.digest() + +def public_key_to_bc_address(public_key): + h160 = hash_160(public_key) + return hash_160_to_bc_address(h160) + +def hash_160_to_bc_address(h160): + vh160 = chr(addrtype) + h160 + h = Hash(vh160) + addr = vh160 + h[0:4] + return b58encode(addr) + +def bc_address_to_hash_160(addr): + bytes = b58decode(addr, 25) + return bytes[1:21] + +__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58base = len(__b58chars) + +def b58encode(v): + """ encode v, which is a string of bytes, to base58. + """ + + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += (256**i) * ord(c) + + result = '' + while long_value >= __b58base: + div, mod = divmod(long_value, __b58base) + result = __b58chars[mod] + result + long_value = div + result = __b58chars[long_value] + result + + # Bitcoin does a little leading-zero-compression: + # leading 0-bytes in the input become leading-1s + nPad = 0 + for c in v: + if c == '\0': nPad += 1 + else: break + + return (__b58chars[0]*nPad) + result + +def b58decode(v, length): + """ decode v into a string of len bytes + """ + long_value = 0L + for (i, c) in enumerate(v[::-1]): + long_value += __b58chars.find(c) * (__b58base**i) + + result = '' + while long_value >= 256: + div, mod = divmod(long_value, 256) + result = chr(mod) + result + long_value = div + result = chr(long_value) + result + + nPad = 0 + for c in v: + if c == __b58chars[0]: nPad += 1 + else: break + + result = chr(0)*nPad + result + if length is not None and len(result) != length: + return None + + return result + +# end of bitcointools base58 implementation + + +# address handling code + +def Hash(data): + return hashlib.sha256(hashlib.sha256(data).digest()).digest() + +def EncodeBase58Check(secret): + hash = Hash(secret) + return b58encode(secret + hash[0:4]) + +def DecodeBase58Check(sec): + vchRet = b58decode(sec, None) + secret = vchRet[0:-4] + csum = vchRet[-4:] + hash = Hash(secret) + cs32 = hash[0:4] + if cs32 != csum: + return None + else: + return secret + +def PrivKeyToSecret(privkey): + if len(privkey) == 279: + return privkey[9:9+32] + else: + return privkey[8:8+32] + +def SecretToASecret(secret, compressed=False): + vchIn = chr((addrtype+128)&255) + secret + if compressed: vchIn += '\01' + return EncodeBase58Check(vchIn) + +def ASecretToSecret(sec): + vch = DecodeBase58Check(sec) + if vch and vch[0] == chr((addrtype+128)&255): + return vch[1:] + else: + return False + +def regenerate_key(sec): + b = ASecretToSecret(sec) + if not b: + return False + b = b[0:32] + secret = int('0x' + b.encode('hex'), 16) + return EC_KEY(secret) + +def GetPubKey(pkey, compressed=False): + return i2o_ECPublicKey(pkey, compressed) + +def GetPrivKey(pkey, compressed=False): + return i2d_ECPrivateKey(pkey, compressed) + +def GetSecret(pkey): + return ('%064x' % pkey.secret).decode('hex') + +def is_compressed(sec): + b = ASecretToSecret(sec) + return len(b) == 33 + +# bitcointools wallet.dat handling code + +def create_env(db_dir): + db_env = DBEnv(0) + r = db_env.open(db_dir, (DB_CREATE|DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_THREAD|DB_RECOVER)) + return db_env + +def parse_CAddress(vds): + d = {'ip':'0.0.0.0','port':0,'nTime': 0} + try: + d['nVersion'] = vds.read_int32() + d['nTime'] = vds.read_uint32() + d['nServices'] = vds.read_uint64() + d['pchReserved'] = vds.read_bytes(12) + d['ip'] = socket.inet_ntoa(vds.read_bytes(4)) + d['port'] = vds.read_uint16() + except: + pass + return d + +def deserialize_CAddress(d): + return d['ip']+":"+str(d['port']) + +def parse_BlockLocator(vds): + d = { 'hashes' : [] } + nHashes = vds.read_compact_size() + for i in xrange(nHashes): + d['hashes'].append(vds.read_bytes(32)) + return d + +def deserialize_BlockLocator(d): + result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') + return result + +def parse_setting(setting, vds): + if setting[0] == "f": # flag (boolean) settings + return str(vds.read_boolean()) + elif setting[0:4] == "addr": # CAddress + d = parse_CAddress(vds) + return deserialize_CAddress(d) + elif setting == "nTransactionFee": + return vds.read_int64() + elif setting == "nLimitProcessors": + return vds.read_int32() + return 'unknown setting' + +class SerializationError(Exception): + """ Thrown when there's a problem deserializing or serializing """ + +class BCDataStream(object): + def __init__(self): + self.input = None + self.read_cursor = 0 + + def clear(self): + self.input = None + self.read_cursor = 0 + + def write(self, bytes): # Initialize with string of bytes + if self.input is None: + self.input = bytes + else: + self.input += bytes + + def map_file(self, file, start): # Initialize with bytes from file + self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) + self.read_cursor = start + def seek_file(self, position): + self.read_cursor = position + def close_file(self): + self.input.close() + + def read_string(self): + # Strings are encoded depending on length: + # 0 to 252 : 1-byte-length followed by bytes (if any) + # 253 to 65,535 : byte'253' 2-byte-length followed by bytes + # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes + # ... and the Bitcoin client is coded to understand: + # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string + # ... but I don't think it actually handles any strings that big. + if self.input is None: + raise SerializationError("call write(bytes) before trying to deserialize") + + try: + length = self.read_compact_size() + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return self.read_bytes(length) + + def write_string(self, string): + # Length-encoded as with read-string + self.write_compact_size(len(string)) + self.write(string) + + def read_bytes(self, length): + try: + result = self.input[self.read_cursor:self.read_cursor+length] + self.read_cursor += length + return result + except IndexError: + raise SerializationError("attempt to read past end of buffer") + + return '' + + def read_boolean(self): return self.read_bytes(1)[0] != chr(0) + def read_int16(self): return self._read_num(' max_version: + print "Version mismatch (must be <= %d)" % max_version + exit(1) + +wallet_addrs = [i['addr'] for i in json_db['keys']] + +if 'json' in opts: + data = [json.dumps(json_db, sort_keys=True, indent=4)] + ext,what = "json","json dump" + +elif 'keys' in opts: + data = sorted([i['sec'] for i in json_db['keys']]) + ext,what = "keys","private keys" + +elif 'addrs' in opts: + data = sorted([i['addr'] for i in json_db['keys']]) + ext,what = "addrs","addresses" + +elif 'keysforaddrs' in opts: + from mmgen.util import get_lines_from_file + usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_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" \ + else "%s:%s" % (len(data),len(wallet_addrs)) + +from mmgen.util import make_chksum_8,write_walletdat_dump_to_file,write_to_stdout +wallet_id = make_chksum_8(str(sorted(wallet_addrs))) + +data = "\n".join(data) + "\n" + +# Output data +if 'stdout' in opts or not sys.stdout.isatty(): + c = False if ('addrs' in opts or not sys.stdout.isatty()) else True + write_to_stdout(data,"secret keys",c) +else: + write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py new file mode 100755 index 00000000..7eeb3297 --- /dev/null +++ b/mmgen/main_tool.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-tool: Perform various Bitcoin-related operations. + Part of the MMGen suite +""" + +import sys +import mmgen.config as g +import mmgen.tool as tool +from mmgen.Opts import * + +help_data = { + 'prog_name': g.prog_name, + 'desc': "Perform various BTC-related operations", + 'usage': "[opts] ", + 'options': """ +-d, --outdir= d Specify an alternate directory 'd' for output +-h, --help Print this help message +-q, --quiet Produce quieter output +-r, --usr-randchars=n Get 'n' characters of additional randomness from + user (min={g.min_urandchars}, max={g.max_urandchars}) +-v, --verbose Produce more verbose output +""".format(g=g), + 'notes': """ + +COMMANDS:{} +Type '{} --help for usage information on a particular +command +""".format(tool.command_help,g.prog_name) +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if len(cmd_args) < 1: + usage(help_data) + sys.exit(1) + +command = cmd_args.pop(0) + +if command not in tool.commands.keys(): + msg("'%s': No such command" % command) + sys.exit(1) + +if cmd_args and cmd_args[0] == '--help': + tool.tool_usage(g.prog_name, command) + sys.exit(0) + +args = tool.process_args(g.prog_name, command, cmd_args) + +tool.opts = opts + +#print command + "(" + ", ".join(args) + ")" +eval("tool." + command + "(" + ", ".join(args) + ")") diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py new file mode 100755 index 00000000..7f7e94d8 --- /dev/null +++ b/mmgen/main_txcreate.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-txcreate: Create a Bitcoin transaction from MMGen- or non-MMGen inputs + to MMGen- or non-MMGen outputs +""" + +import sys +from decimal import Decimal + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.license import * +from mmgen.tx import * +from mmgen.util import msg, msg_r, user_confirm + +help_data = { + 'prog_name': g.prog_name, + 'desc': "Create a BTC transaction with outputs to specified addresses", + 'usage': "[opts] ... [change addr] [addr file] ...", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-e, --echo-passphrase Print passphrase to screen when typing it +-f, --tx-fee= f Transaction fee (default: {g.tx_fee} BTC) +-i, --info Display unspent outputs and exit +-q, --quiet Suppress warnings; overwrite files without + prompting +""".format(g=g), + 'notes': """ + +Transaction inputs are chosen from a list of the user's unpent outputs +via an interactive menu. + +Ages of transactions are approximate based on an average block creation +interval of {g.mins_per_block} minutes. + +Addresses on the command line can be Bitcoin addresses or {pnm} addresses +of the form :. + +To send all inputs (minus TX fee) to a single output, specify one address +with no amount on the command line. +""".format(g=g,pnm=g.proj_name) +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if g.debug: show_opts_and_cmd_args(opts,cmd_args) + +c = connect_to_bitcoind() + +if not 'info' in opts: + do_license_msg(immed=True) + + tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],"" + + addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext] + cmd_args = set(cmd_args) - set(addrfiles) + + for a in addrfiles: + check_infile(a) + addr_data.append(parse_addrs_file(a)) + + def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map): + # assume mmaddr has already been checked + btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data) + if not btcaddr: + if addr_data: + btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data) + else: + msg(txmsg['addrfile_no_data_msg'] % mmaddr) + sys.exit(2) + + b2m_map[btcaddr] = mmaddr,label + return btcaddr + + for a in cmd_args: + if "," in a: + a1,a2 = a.split(",") + if is_btc_addr(a1): + btcaddr = a1 + elif is_mmgen_addr(a1): + btcaddr = mmaddr2btcaddr(c,a1,acct_data,addr_data,b2m_map) + else: + msg("%s: unrecognized subargument in argument '%s'" % (a1,a)) + sys.exit(2) + + if is_btc_amt(a2): + tx_out[btcaddr] = normalize_btc_amt(a2) + else: + msg("%s: invalid amount in argument '%s'" % (a2,a)) + sys.exit(2) + elif is_mmgen_addr(a) or is_btc_addr(a): + if change_addr: + msg("ERROR: More than one change address specified: %s, %s" % + (change_addr, a)) + sys.exit(2) + change_addr = a if is_btc_addr(a) else \ + mmaddr2btcaddr(c,a,acct_data,addr_data,b2m_map) + tx_out[change_addr] = 0 + else: + msg("%s: unrecognized argument" % a) + sys.exit(2) + + if not tx_out: + msg("At least one output must be specified on the command line") + sys.exit(2) + + tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee + tx_fee = normalize_btc_amt(tx_fee) + if tx_fee > g.max_tx_fee: + msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee)) + sys.exit(2) + +if g.debug: show_opts_and_cmd_args(opts,cmd_args) + +#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit() + +#if False: +if g.bogus_wallet_data: + import mmgen.rpc + us = eval(get_data_from_file(g.bogus_wallet_data)) +else: + us = c.listunspent() + +if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2) + +unspent = sort_and_view(us,opts) + +total = trim_exponent(sum([i.amount for i in unspent])) + +msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent))) +if 'info' in opts: sys.exit(0) + +send_amt = sum([tx_out[i] for i in tx_out.keys()]) +msg("Total amount to spend: %s%s" % ( + (send_amt or "Unknown")," BTC" if send_amt else "")) + +while True: + sel_nums = select_outputs(unspent, + "Enter a range or space-separated list of outputs to spend: ") + msg("Selected output%s: %s" % + (("" if len(sel_nums) == 1 else "s"), " ".join(str(i) for i in sel_nums)) + ) + sel_unspent = [unspent[i-1] for i in sel_nums] + + mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent]) + mmaddrs.discard("") + + if mmaddrs and len(mmaddrs) < len(sel_unspent): + msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs))) + if not user_confirm("Accept?"): + continue + + total_in = trim_exponent(sum([i.amount for i in sel_unspent])) + change = trim_exponent(total_in - (send_amt + tx_fee)) + + if change >= 0: + prompt = "Transaction produces %s BTC in change. OK?" % change + if user_confirm(prompt,default_yes=True): + break + else: + msg(txmsg['not_enough_btc'] % change) + +if change > 0 and not change_addr: + msg(txmsg['throwaway_change'] % change) + sys.exit(2) + +if change_addr in tx_out and not change: + msg("Warning: Change address will be unused as transaction produces no change") + del tx_out[change_addr] + +for k,v in tx_out.items(): tx_out[k] = float(v) + +if change > 0: tx_out[change_addr] = float(change) + +tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent] + +if g.debug: + print "tx_in:", repr(tx_in) + print "tx_out:", repr(tx_out) + +tx_hex = c.createrawtransaction(tx_in,tx_out) +qmsg("Transaction successfully created") +prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager" +reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) + +if reply and reply in "YyVv": + pager = True if reply in "Vv" else False + view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager) + +prompt = "Save transaction?" +if user_confirm(prompt,default_yes=True): + amt = send_amt or change + tx_id = make_chksum_6(unhexlify(tx_hex)).upper() + outfile = "tx_%s[%s].%s" % (tx_id,amt,g.rawtx_ext) + data = "{} {} {}\n{}\n{}\n{}\n".format( + tx_id, amt, make_timestamp(), + tx_hex, + repr([i.__dict__ for i in sel_unspent]), + repr(b2m_map) + ) + write_to_file(outfile,data,opts,"transaction",False,True) +else: + msg("Transaction not saved") diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py new file mode 100755 index 00000000..85d2604c --- /dev/null +++ b/mmgen/main_txsend.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network +""" + +import sys + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.license import * +from mmgen.tx import * +from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit + +help_data = { + 'prog_name': g.prog_name, + 'desc': "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()), + 'usage': "[opts] ", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-q, --quiet Suppress warnings; overwrite files without prompting +""" +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if len(cmd_args) == 1: + infile = cmd_args[0]; check_infile(infile) +else: usage(help_data) + +# Begin execution + +do_license_msg() + +tx_data = get_lines_from_file(infile,"signed transaction data") + +metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,infile) + +qmsg("Signed transaction file '%s' is valid" % infile) + +c = connect_to_bitcoind() + +prompt = "View transaction data? (y)es, (N)o, (v)iew in pager" +reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) +if reply and reply in "YyVv": + p = True if reply in "Vv" else False + view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p) + +warn = "Once this transaction is sent, there's no taking it back!" +what = "broadcast this transaction to the network" +expect = "YES, I REALLY WANT TO DO THIS" + +if g.quiet: warn,expect = "","YES" + +confirm_or_exit(warn, what, expect) + +msg("Sending transaction") + +try: + tx_id = c.sendrawtransaction(tx_hex) +except: + msg("Unable to send transaction") + sys.exit(3) + +msg("Transaction sent: %s" % tx_id) + +of = "tx_{}[{}].out".format(*metadata[:2]) +write_to_file(of, tx_id+"\n",opts,"transaction ID",True,True) diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py new file mode 100755 index 00000000..940c08e8 --- /dev/null +++ b/mmgen/main_txsign.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-txsign: Sign a transaction generated by 'mmgen-txcreate' +""" + +import sys + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.license import * +from mmgen.tx import * +from mmgen.util import msg,qmsg + +help_data = { + 'prog_name': g.prog_name, + 'desc': "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()), + 'usage': "[opts] .. [mmgen wallet/seed/words/brainwallet file] .. [addrfile] ..", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-e, --echo-passphrase Print passphrase to screen when typing it +-i, --info Display information about the transaction and exit +-I, --tx-id Display transaction ID and exit +-k, --keys-from-file= f Provide additional keys for non-{pnm} addresses +-K, --all-keys-from-file=f Like '-k', only use the keyfile as key source + for ALL inputs, including {pnm} ones. Can be used + for online signing without an {pnm} seed source. + {pnm}-to-BTC mappings can optionally be verified + using address file(s) listed on the command line +-P, --passwd-file= f Get passphrase from file 'f' +-q, --quiet Suppress warnings; overwrite files without + prompting +-v, --verbose Produce more verbose output +-V, --skip-key-preverify Skip optional key pre-verification step +-b, --from-brain= l,p Generate keys from a user-created password, + i.e. a "brainwallet", using seed length 'l' and + hash preset 'p' +-w, --use-wallet-dat Get keys from a running bitcoind +-g, --from-incog Generate keys from an incognito wallet +-X, --from-incog-hex Generate keys from an incognito hexadecimal wallet +-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file + 'f' at offset 'o', with seed length of 'l' +-m, --from-mnemonic Generate keys from an electrum-like mnemonic +-s, --from-seed Generate keys from a seed in .{g.seed_ext} format +""".format(g=g,pnm=g.proj_name), + 'notes': """ + +Transactions with either {pnm} or non-{pnm} input addresses may be signed. +For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source. +For {pnm} inputs, key data is generated from your seed as with the +{pnl}-addrgen and {pnl}-keygen utilities. + +Data for the --from- options will be taken from a file if a second +file is specified on the command line. Otherwise, the user will be +prompted to enter the data. + +In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm} +keys must be supplied in a separate file (WIF format, one key per line) +using the '--keys-from-file' option. Alternatively, one may get keys from +a running bitcoind using the '--force-wallet-dat' option. First import the +required {pnm} keys using 'bitcoind importprivkey'. + +For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address +mappings are verified. Therefore, seed material for these addresses must +be supplied on the command line (but see '--all-keys-from-file'). + +Seed data supplied in files must have the following extensions: + wallet: '.{g.wallet_ext}' + seed: '.{g.seed_ext}' + mnemonic: '.{g.mn_ext}' + brainwallet: '.{g.brain_ext}' +""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower()) +} + +opts,infiles = parse_opts(sys.argv,help_data) + +for l in ( +('tx_id', 'info'), +('keys_from_file','all_keys_from_file') +): warn_incompatible_opts(opts,l) + +if 'from_incog_hex' in opts or 'from_incog_hidden' in opts: + opts['from_incog'] = True +if 'all_keys_from_file' in opts: + opts['keys_from_file'] = opts['all_keys_from_file'] +# opts['skip_key_preverify'] = True + +if not infiles: usage(help_data) +for i in infiles: check_infile(i) + +c = connect_to_bitcoind() + +saved_seeds = {} +tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext] +addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext] +infiles = list(set(infiles) - set(tx_files) - set(addrfiles)) + +if not "info" in opts: do_license_msg(immed=True) + +if 'keys_from_file' in opts: + from mmgen.crypto import mmgen_decrypt + fn = opts['keys_from_file'] + d = get_data_from_file(fn,"keylist") + if get_extension(fn) == g.mmenc_ext or not \ + is_b58_str(remove_comments(d.split("\n"))[0][:55]): + qmsg("Keylist appears to be encrypted") + while True: + hp = get_hash_preset_from_user('3') + d_dec = mmgen_decrypt(d,"encrypted keylist",hp,opts) + if d_dec: d = d_dec; break + else: msg("Trying again...") + keys_from_file = remove_comments(d.split("\n")) +else: keys_from_file = [] + +for tx_file in tx_files: + m = "" if 'tx_id' in opts else "transaction data" + tx_data = get_lines_from_file(tx_file,m) + + metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file) + qmsg("Successfully opened transaction file '%s'" % tx_file) + + if 'tx_id' in opts: + msg(metadata[0]) + sys.exit(0) + + if 'info' in opts: + view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata) + sys.exit(0) + +# Are inputs mmgen addresses? + mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]] + other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]] + + if 'all_keys_from_file' in opts: other_inputs = inputs_data + + keys = keys_from_file + + if other_inputs and not keys and not 'use_wallet_dat' in opts: + missing_keys_errormsg(other_inputs) + sys.exit(2) + + if other_inputs and keys and not 'skip_key_preverify' in opts: + a = [i['address'] for i in other_inputs] + preverify_keys(a, keys) + opts['skip_key_preverify'] = True + + if 'all_keys_from_file' in opts: + if addrfiles: + check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles) + else: + confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue") + else: + check_mmgen_to_btc_addr_mappings( + mmgen_inputs,b2m_map,infiles,saved_seeds,opts) + + if len(tx_files) > 1: + msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files))) + + prompt = "View transaction data? (y)es, (N)o, (v)iew in pager" + reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) + if reply and reply in "YyVv": + p = True if reply in "Vv" else False + view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p) + + sig_data = [ + {"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']} + for i in inputs_data] + + if mmgen_inputs and not 'all_keys_from_file' in opts: + ml = [i['account'].split()[0] for i in mmgen_inputs] + keys += get_keys_for_mmgen_addrs(ml,infiles,saved_seeds,opts) + + if 'use_wallet_dat' in opts: + sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) + else: + sig_tx = sign_transaction(c,tx_hex,sig_data,keys) + elif other_inputs: + if keys: + sig_tx = sign_transaction(c,tx_hex,sig_data,keys) + else: + sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts) + + if sig_tx['complete']: + prompt = "OK\nSave signed transaction?" + if user_confirm(prompt,default_yes=True): + outfile = "tx_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext) + data = "{}\n{}\n{}\n{}\n".format( + " ".join(metadata[:2] + [make_timestamp()]), + sig_tx['hex'], + repr(inputs_data), + repr(b2m_map) + ) + write_to_file(outfile,data,opts,"signed transaction",True,True) + else: + msg("failed\nSome keys were missing. Transaction could not be signed.") + sys.exit(3) diff --git a/mmgen/main_walletchk.py b/mmgen/main_walletchk.py new file mode 100755 index 00000000..22859d04 --- /dev/null +++ b/mmgen/main_walletchk.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-walletchk: Check integrity of an MMGen deterministic wallet, display + information about it and export it to various formats +""" + +import sys +import mmgen.config as g +from mmgen.Opts import * +from mmgen.util import * +from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data + +help_data = { + 'prog_name': g.prog_name, + 'desc': """Check integrity of an {} deterministic wallet, display + its information, and export seed and mnemonic data. + """.format(g.proj_name), + 'usage': "[opts] [filename]", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-e, --echo-passphrase Print passphrase to screen when typing it +-P, --passwd-file= f Get passphrase from file 'f' +-q, --quiet Suppress warnings; overwrite files without prompting +-r, --usr-randchars= n Get 'n' characters of additional randomness from + user (min={g.min_urandchars}, max={g.max_urandchars}) +-S, --stdout Print seed or mnemonic data to standard output +-v, --verbose Produce more verbose output +-g, --export-incog Export wallet to incognito format +-X, --export-incog-hex Export wallet to incognito hexadecimal format +-G, --export-incog-hidden=f,o Hide incognito data in existing file 'f' + at offset 'o' (comma-separated) +-m, --export-mnemonic Export the wallet's mnemonic to file +-s, --export-seed Export the wallet's seed to file +""".format(g=g), + 'notes': """ + +Since good randomness is particularly important for incognito wallets, +the '--usr-randchars' option is turned on by default to gather additional +entropy from the user when one of the '--export-incog*' options is +selected. If you fully trust your OS's random number generator and wish +to disable this option, then specify '-r0' on the command line. +""" +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if 'export_incog_hidden' in opts or 'export_incog_hex' in opts: + opts['export_incog'] = True + +if len(cmd_args) != 1: usage(help_data) + +check_infile(cmd_args[0]) + +if 'export_mnemonic' in opts: + qmsg("Exporting mnemonic data to file by user request") +elif 'export_seed' in opts: + qmsg("Exporting seed data to file by user request") +elif 'export_incog' in opts: + if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl + qmsg("Exporting wallet to incognito format by user request") + incog_enc,seed_id,key_id,iv_id,preset = \ + wallet_to_incog_data(cmd_args[0],opts) + + if "export_incog_hidden" in opts: + export_to_hidden_incog(incog_enc,opts) + else: + seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8 + fn = "%s-%s-%s[%s,%s].%s" % ( + seed_id, key_id, iv_id, seed_len, preset, + g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext + ) + data = pretty_hexdump(incog_enc,2,8,line_nums=False) \ + if "export_incog_hex" in opts else incog_enc + export_to_file(fn, data, opts, "incognito wallet data") + + sys.exit() + +seed = get_seed_from_wallet(cmd_args[0], opts) +if seed: qmsg("Wallet is OK") +else: + msg("Error opening wallet") + sys.exit(2) + +if 'export_mnemonic' in opts: + wl = get_default_wordlist() + from mmgen.mnemonic import get_mnemonic_from_seed + p = True if g.debug else False + mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p) + fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext) + export_to_file(fn, " ".join(mn)+"\n", opts, "mnemonic data") + +elif 'export_seed' in opts: + from mmgen.bitcoin import b58encode_pad + data = col4(b58encode_pad(seed)) + chk = make_chksum_6(b58encode_pad(seed)) + fn = "%s.%s" % (make_chksum_8(seed).upper(), g.seed_ext) + export_to_file(fn, "%s %s\n" % (chk,data), opts, "seed data") diff --git a/mmgen/main_walletgen.py b/mmgen/main_walletgen.py new file mode 100755 index 00000000..4a16cae7 --- /dev/null +++ b/mmgen/main_walletgen.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2014 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-walletgen: Generate an MMGen deterministic wallet +""" + +import sys, os +from hashlib import sha256 + +import mmgen.config as g +from mmgen.Opts import * +from mmgen.license import * +from mmgen.util import * +from mmgen.crypto import * + +help_data = { + 'prog_name': g.prog_name, + 'desc': "Generate an {} deterministic wallet".format(g.proj_name), + 'usage': "[opts] [infile]", + 'options': """ +-h, --help Print this help message +-d, --outdir= d Specify an alternate directory 'd' for output +-e, --echo-passphrase Print passphrase to screen when typing it +-H, --show-hash-presets Show information on available hash presets +-l, --seed-len= n Create seed of length 'n'. Options: {seed_lens} + (default: {g.seed_len}) +-L, --label= l Label to identify this wallet (32 chars max. + Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".") +-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' + (default: '{g.hash_preset}') +-P, --passwd-file= f Get passphrase from file 'f' +-q, --quiet Produce quieter output; overwrite files without + prompting +-r, --usr-randchars= n Get 'n' characters of additional randomness from + user (min={g.min_urandchars}, max={g.max_urandchars}) +-v, --verbose Produce more verbose output + +-b, --from-brain= l,p Generate wallet from a user-created passphrase, + i.e. a "brainwallet", using seed length 'l' and + hash preset 'p' (comma-separated) +-g, --from-incog Generate wallet from an incognito-format wallet +-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic +-s, --from-seed Generate wallet from a seed in .{g.seed_ext} format +""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g), + 'notes': """ + +By default (i.e. when invoked without any of the '--from-' options), +{g.prog_name} generates a wallet based on a random seed. + +Data for the --from- options will be taken from if +is specified. Otherwise, the user will be prompted to enter the data. + +For passphrases all combinations of whitespace are equal, and leading and +trailing space are ignored. This permits reading passphrase data from a +multi-line file with free spacing and indentation. This is particularly +convenient for long brainwallet passphrases, for example. + +Since good randomness is particularly important when generating wallets, +the '--usr-randchars' option is turned on by default to gather additional +entropy from the user. If you fully trust your OS's random number gener- +ator and wish to disable this option, specify '-r0' on the command line. + +BRAINWALLET NOTE: + +As brainwallets require especially strong hashing to thwart dictionary +attacks, the brainwallet hash preset must be specified by the user, using +the 'p' parameter of the '--from-brain' option. This preset should be +stronger than the one used for hashing the seed (i.e. the default value or +the one specified in the '--hash-preset' option). + +The '--from-brain' option also requires the user to specify a seed length +(the 'l' parameter), which overrides both the default and any one given in +the '--seed-len' option. + +For a brainwallet passphrase to always generate the same keys and +addresses, the same 'l' and 'p' parameters to '--from-brain' must be used +in all future invocations with that passphrase. +""".format(g=g) +} + +opts,cmd_args = parse_opts(sys.argv,help_data) + +if 'show_hash_presets' in opts: show_hash_presets() +if opts['usr_randchars'] == -1: opts['usr_randchars'] = g.usr_randchars_dfl + +if g.debug: show_opts_and_cmd_args(opts,cmd_args) + +if len(cmd_args) == 1: + infile = cmd_args[0] + check_infile(infile) + ext = infile.split(".")[-1] + ok_exts = g.seedfile_exts + for e in ok_exts: + if e == ext: break + else: + msg( +"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts)) + sys.exit(1) +elif len(cmd_args) == 0: + infile = "" +else: usage(help_data) + +# Begin execution + +do_license_msg() + +if 'from_brain' in opts and not g.quiet: + confirm_or_exit(cmessages['brain_warning'].format( + g.proj_name, *get_from_brain_opt_params(opts)), + "continue") + +for i in 'from_mnemonic','from_brain','from_seed','from_incog': + if infile or (i in opts): + seed = get_seed_retry(infile,opts) + if "from_incog" in opts or get_extension(infile) == g.incog_ext: + qmsg(cmessages['incog'] % make_chksum_8(seed)) + else: qmsg("") + break +else: + # Truncate random data for smaller seed lengths + seed = sha256(get_random(128,opts)).digest()[:opts['seed_len']/8] + +salt = sha256(get_random(128,opts)).digest()[:g.salt_len] + +qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset']) + +passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts) + +key = make_key(passwd, salt, opts['hash_preset']) + +enc_seed = encrypt_seed(seed, key) + +write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts) diff --git a/mmgen/mn_electrum.py b/mmgen/mn_electrum.py index 2e99709d..39740144 100755 --- a/mmgen/mn_electrum.py +++ b/mmgen/mn_electrum.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 diff --git a/mmgen/mn_tirosh.py b/mmgen/mn_tirosh.py index 814ea12a..3354ab44 100755 --- a/mmgen/mn_tirosh.py +++ b/mmgen/mn_tirosh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 diff --git a/mmgen/mnemonic.py b/mmgen/mnemonic.py index e4ea611e..7fad9a1f 100755 --- a/mmgen/mnemonic.py +++ b/mmgen/mnemonic.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -mnemonic.py: Mnemomic routines for the mmgen suite +mnemonic.py: Mnemomic routines for the MMGen suite """ wl_checksums = { diff --git a/mmgen/term.py b/mmgen/term.py index 933554dc..14efbaae 100755 --- a/mmgen/term.py +++ b/mmgen/term.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # 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 +term.py: Terminal-handling routines for the MMGen suite """ import sys, os, struct @@ -30,16 +31,12 @@ def _kb_hold_protect_unix(): timeout = float(0.3) - try: - while True: - key = select([sys.stdin], [], [], timeout)[0] - if key: sys.stdin.read(1) - else: break - except KeyboardInterrupt: - msg("\nUser interrupt") - sys.exit(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) + while True: + key = select([sys.stdin], [], [], timeout)[0] + if key: sys.stdin.read(1) + else: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + break def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True): @@ -51,44 +48,35 @@ def _get_keypress_unix(prompt="",immed_chars="",prehold_protect=True): old = termios.tcgetattr(fd) tty.setcbreak(fd) - try: - while True: - # Protect against held-down key before read() - key = select([sys.stdin], [], [], timeout)[0] - ch = sys.stdin.read(1) - if prehold_protect: - if key: continue - if immed_chars == "ALL" or ch in immed_chars: - return ch - if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": - return ch - # Protect against long keypress - key = select([sys.stdin], [], [], timeout)[0] + while True: + # Protect against held-down key before read() + key = select([sys.stdin], [], [], timeout)[0] + ch = sys.stdin.read(1) + if prehold_protect: if key: continue - else: return ch - except KeyboardInterrupt: - msg("\nUser interrupt") - sys.exit(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old) + if immed_chars == "ALL" or ch in immed_chars: break + if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r": break + # Protect against long keypress + key = select([sys.stdin], [], [], timeout)[0] + if not key: break + + termios.tcsetattr(fd, termios.TCSADRAIN, old) + return ch + def _kb_hold_protect_mswin(): timeout = float(0.5) - try: + while True: + hit_time = time.time() while True: - 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) + if msvcrt.kbhit(): + msvcrt.getch() + break + if float(time.time() - hit_time) > timeout: + return def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True): @@ -96,27 +84,23 @@ def _get_keypress_mswin(prompt="",immed_chars="",prehold_protect=True): msg_r(prompt) timeout = float(0.5) - try: - while True: - if msvcrt.kbhit(): - ch = msvcrt.getch() + while True: + if msvcrt.kbhit(): + ch = msvcrt.getch() - if ord(ch) == 3: raise KeyboardInterrupt + if ord(ch) == 3: raise KeyboardInterrupt - if immed_chars == "ALL" or ch in immed_chars: + 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 - 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(): @@ -221,13 +205,7 @@ def do_pager(text): p = Popen([pager], stdin=PIPE, shell=shell) except: pass else: - try: - p.communicate(text+end+"\n") - except KeyboardInterrupt: - # Has no effect. Why? - if pager != "less": - msg("\n(User interrupt)\n") - finally: - msg_r("\r") - break + p.communicate(text+end+"\n") + msg_r("\r") + break else: print text+end diff --git a/mmgen/tests/bitcoin.py b/mmgen/tests/bitcoin.py index 1c7288c7..55036a9d 100755 --- a/mmgen/tests/bitcoin.py +++ b/mmgen/tests/bitcoin.py @@ -38,26 +38,23 @@ def b58_randenc(): sys.exit(9) def keyconv_compare_randloop(loops, quiet=False): - try: - for i in range(1,int(loops)+1): + for i in range(1,int(loops)+1): - wif = numtowif_rand(quiet=True) + wif = numtowif_rand(quiet=True) - if not quiet: sys.stderr.write("-- %s --\n" % i) - ret = keyconv_compare(wif,quiet) - if ret == False: sys.exit(9) - - if quiet: - sys.stderr.write("\riteration: %i " % i) + if not quiet: sys.stderr.write("-- %s --\n" % i) + ret = keyconv_compare(wif,quiet) + if ret == False: sys.exit(9) if quiet: - sys.stderr.write("\r%s iterations completed\n" % i) - else: - print "%s iterations completed" % i + sys.stderr.write("\riteration: %i " % i) + + if quiet: + sys.stderr.write("\r%s iterations completed\n" % i) + else: + print "%s iterations completed" % i - except KeyboardInterrupt: - msg("\nUser interrupt") def keyconv_compare(wif,quiet=False): do_msg = nomsg if quiet else msg @@ -144,17 +141,14 @@ def b58tohex_pad(s_in, quiet=False): b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet) def hextob58_pad_randloop(loops, quiet=False): - try: - for i in range(1,int(loops)+1): - r = hexlify(get_random(32)) - hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet) - if not quiet: print - if not i % 100 and quiet: - sys.stderr.write("\riteration: %i " % i) + for i in range(1,int(loops)+1): + r = hexlify(get_random(32)) + hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet) + 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 KeyboardInterrupt: - msg("\nUser interrupt") + sys.stderr.write("\r%s iterations completed\n" % i) def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif): print "Input: %s" % s_in diff --git a/mmgen/tool.py b/mmgen/tool.py index 13cb802f..78c0f371 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ -tool.py: Routines and data for the mmgen-tool utility +tool.py: Routines and data for the 'mmgen-tool' utility """ import sys @@ -415,9 +416,9 @@ def hex2wif(hexpriv,compressed=False): print bitcoin.hextowif(hexpriv,compressed) -def encrypt(infile,outfile="",hash_preset=''): +def encrypt(infile,outfile="",hash_preset='3'): data = get_data_from_file(infile,"data for encryption") - enc_d = mmgen_encrypt(data,hash_preset,opts) + enc_d = mmgen_encrypt(data,"",hash_preset,opts) if outfile == '-': write_to_stdout(enc_d,"encrypted data",confirm=True) else: @@ -426,9 +427,9 @@ def encrypt(infile,outfile="",hash_preset=''): write_to_file(outfile, enc_d, opts,"encrypted data",True,True) -def decrypt(infile,outfile="",hash_preset=''): +def decrypt(infile,outfile="",hash_preset='3'): enc_d = get_data_from_file(infile,"encrypted data") - dec_d = mmgen_decrypt(enc_d,hash_preset,opts) + dec_d = mmgen_decrypt(enc_d,"",hash_preset,opts) if outfile == '-': write_to_stdout(dec_d,"decrypted data",confirm=True) else: diff --git a/mmgen/tx.py b/mmgen/tx.py index eb76b930..c0f45df1 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ tx.py: Bitcoin transaction routines """ @@ -453,6 +454,12 @@ def is_btc_addr(s): from mmgen.bitcoin import verify_addr return verify_addr(s) +def is_b58_str(s): + from mmgen.bitcoin import b58a + for ch in s: + if ch not in b58a: return False + return True + def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data): @@ -720,8 +727,7 @@ def preverify_keys(addrs_orig, keys_orig): if addrs: s = "" if len(addrs) == 1 else "es" - msg("No keys found for the following non-%s address%s:" % - (g.proj_name,s)) + msg("No keys found for the following address%s:" % s) print " %s" % "\n ".join(addrs) sys.exit(2) diff --git a/mmgen/util.py b/mmgen/util.py index 2fecd8c5..922ee9d6 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution -# Copyright (C) 2013-2014 by philemon +# Copyright (C)2013-2014 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 @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + """ util.py: Low-level routines imported by other modules for the MMGen suite """ @@ -342,11 +343,11 @@ def write_to_stdout(data, what, confirm=True): sys.stdout.write(data) -def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False): +def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False): if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile) - if confirm: + if confirm_overwrite: from os import stat try: stat(outfile) @@ -370,11 +371,9 @@ def export_to_file(outfile, data, opts, what="data"): if 'stdout' in opts: write_to_stdout(data, what, confirm=True) - elif not sys.stdout.isatty(): - write_to_stdout(data, what, confirm=False) else: - c = False if g.quiet else True - write_to_file(outfile,data,opts,what,c,True) + confirm_overwrite = False if g.quiet else True + write_to_file(outfile,data,opts,what,confirm_overwrite,True) from mmgen.bitcoin import b58decode_pad,b58encode_pad @@ -430,9 +429,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts): outfile="{}-{}[{},{}].{}".format( seed_id,key_id,seed_len,hash_preset,g.wallet_ext) - c = False if g.quiet else True d = "\n".join((chk,)+lines)+"\n" - write_to_file(outfile,d,opts,"wallet",c,True) + confirm_overwrite = False if g.quiet else True + write_to_file(outfile,d,opts,"wallet",confirm_overwrite,True) if g.verbose: display_control_data(label,metadata,hash_preset,salt,enc_seed) @@ -684,22 +683,29 @@ def export_to_hidden_incog(incog_enc,opts): msg("Data written to file '%s' at offset %s" % (os.path.relpath(outfile),offset)) - from mmgen.term import kb_hold_protect,get_char +def get_hash_preset_from_user(hp='3'): + p = "Enter hash preset, or hit ENTER to accept the default ('%s'): " % hp + while True: + ret = my_raw_input(p) + if ret: + if ret in g.hash_presets.keys(): return ret + else: + msg("Invalid input. Valid choices are %s" % + ", ".join(sorted(g.hash_presets.keys()))) + continue + else: return hp + + def my_raw_input(prompt,echo=True): msg_r(prompt) kb_hold_protect() - try: - if echo: - reply = raw_input("") - else: - from getpass import getpass - reply = getpass("") - except KeyboardInterrupt: - msg("\nUser interrupt") - sys.exit(1) - + if echo: + reply = raw_input("") + else: + from getpass import getpass + reply = getpass("") kb_hold_protect() return reply