From 75ab55a2d34b1466564183e2be3ffc8e3227560d Mon Sep 17 00:00:00 2001 From: philemon Date: Sun, 27 Jul 2014 15:40:27 +0400 Subject: [PATCH] Bugfixes, new commands for 'mmgen-tool' --- mmgen-txcreate | 2 +- mmgen-txsign | 2 +- mmgen-walletgen | 10 +++- mmgen/Opts.py | 6 ++- mmgen/bitcoin.py | 30 +++++++---- mmgen/config.py | 8 +-- mmgen/tool.py | 133 +++++++++++++++++++++++++++++++++-------------- mmgen/tx.py | 11 ++-- mmgen/util.py | 11 ++-- 9 files changed, 138 insertions(+), 75 deletions(-) diff --git a/mmgen-txcreate b/mmgen-txcreate index 1d064f6e..6d5f7e47 100755 --- a/mmgen-txcreate +++ b/mmgen-txcreate @@ -137,7 +137,7 @@ if not 'info' in opts: do_license_msg(immed=True) #write_to_file("bogus_unspent.json", repr(us)); sys.exit() if g.bogus_wallet_data: import mmgen.rpc - us = eval(get_data_from_file("bogus_unspent.json")) + us = eval(get_data_from_file(g.bogus_wallet_data)) else: us = c.listunspent() diff --git a/mmgen-txsign b/mmgen-txsign index 4e076c5a..98025c21 100755 --- a/mmgen-txsign +++ b/mmgen-txsign @@ -107,6 +107,7 @@ for tx_file in tx_files: 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]) @@ -135,7 +136,6 @@ for tx_file in tx_files: if len(tx_files) > 1: msg("\nTransaction %s/%s:" % (tx_files.index(tx_file)+1,len(tx_files))) - qmsg("Successfully opened transaction file '%s'" % tx_file) prompt = "View transaction data? (y)es, (N)o, (v)iew in pager" reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True) diff --git a/mmgen-walletgen b/mmgen-walletgen index eecc83ed..38380ce0 100755 --- a/mmgen-walletgen +++ b/mmgen-walletgen @@ -141,6 +141,11 @@ if g.debug: display_user_random_data(usr_keys,key_timings) usr_rand_data = sha256(usr_keys).digest() + \ sha256("".join(key_timings)).digest() +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) @@ -156,11 +161,12 @@ else: salt = os_rand_data[1] + usr_rand_data salt = sha256(salt).digest()[:g.salt_len] -qmsg("""Now you must choose a passphrase to encrypt the seed with. A key will be +qmsg(""" +Now you must choose a passphrase to encrypt the wallet with. A key will be generated from your passphrase using a hash preset of '%s'. Please note that no strength checking of passphrases is performed. For an empty passphrase, just hit ENTER twice. -""" % opts['hash_preset']) +""".strip() % opts['hash_preset']) passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts) diff --git a/mmgen/Opts.py b/mmgen/Opts.py index 905caa0a..aaf74f8a 100755 --- a/mmgen/Opts.py +++ b/mmgen/Opts.py @@ -94,6 +94,8 @@ def parse_opts(argv,help_data): ) opts,infiles = process_opts(argv,help_data,short_opts,long_opts) + if g.debug: print "processed user opts: %s" % opts + if not check_opts(opts,long_opts): sys.exit(1) # MMGen only! return opts,infiles @@ -121,8 +123,8 @@ def check_opts(opts,long_opts): # Check for file existence and readability if opt in ('keys_from_file','addrlist','passwd_file','keysforaddrs'): - check_infile(val) - return True + check_infile(val) # exits on error + continue if opt == 'outdir': what = "output directory" diff --git a/mmgen/bitcoin.py b/mmgen/bitcoin.py index 431f7ab2..746eb6c5 100755 --- a/mmgen/bitcoin.py +++ b/mmgen/bitcoin.py @@ -51,17 +51,22 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # The "zero address": # 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate) # -def pubhex2addr(pubhex): +def pubhex2hexaddr(pubhex): step1 = sha256(unhexlify(pubhex)).digest() - step2 = hashlib_new('ripemd160',step1).hexdigest() + return hashlib_new('ripemd160',step1).hexdigest() + +def hexaddr2addr(hexaddr): # See above: - extra_ones = (len(step2) - len(step2.lstrip("0"))) / 2 - step3 = sha256(unhexlify('00'+step2)).digest() - step4 = sha256(step3).hexdigest() - pubkey = int(step2 + step4[:8], 16) + extra_ones = (len(hexaddr) - len(hexaddr.lstrip("0"))) / 2 + step1 = sha256(unhexlify('00'+hexaddr)).digest() + step2 = sha256(step1).hexdigest() + pubkey = int(hexaddr + step2[:8], 16) return "1" + ("1" * extra_ones) + _numtob58(pubkey) -def verify_addr(addr,verbose=False): +def pubhex2addr(pubhex): + return hexaddr2addr(pubhex2hexaddr(pubhex)) + +def verify_addr(addr,verbose=False,return_hex=False): if addr[0] != "1": if verbose: print "%s: Invalid address" % addr @@ -78,7 +83,7 @@ def verify_addr(addr,verbose=False): if verbose: print "Invalid checksum in address %s" % ("1" + addr) return False - return True + return addr_hex[:40] if return_hex else True # Reworked code from here: @@ -172,14 +177,17 @@ def hextowif(hexpriv,compressed=False): key = step1 + step3[:8] return _numtob58(int(key,16)) -def privnum2addr(numpriv,compressed=False): +def privnum2pubhex(numpriv,compressed=False): pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1) pubkey = hexlify(pko.get_verifying_key().to_string()) if compressed: p = '03' if pubkey[-1] in "13579bdf" else '02' - return pubhex2addr(p+pubkey[:64]) + return p+pubkey[:64] else: - return pubhex2addr('04'+pubkey) + return '04'+pubkey + +def privnum2addr(numpriv,compressed=False): + return pubhex2addr(privnum2pubhex(numpriv,compressed)) # Used only in test suite. To check validity, recode with numtowif() def wiftonum(wifpriv): diff --git a/mmgen/config.py b/mmgen/config.py index f8af352f..0f98ba4a 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -22,7 +22,7 @@ config.py: Constants and configuration options for the mmgen suite author = "Philemon" email = "" Cdates = '2013-2014' -version = '0.7.4' +version = '0.7.5' quiet,verbose = False,False min_screen_width = 80 @@ -62,9 +62,9 @@ http_timeout = 30 keyconv_exec = "keyconv" from os import getenv -debug = True if getenv("MMGEN_DEBUG") else False -no_license = True if getenv("MMGEN_NOLICENSE") else False -bogus_wallet_data = True if getenv("MMGEN_BOGUS_WALLET_DATA") else False +debug = getenv("MMGEN_DEBUG") +no_license = getenv("MMGEN_NOLICENSE") +bogus_wallet_data = getenv("MMGEN_BOGUS_WALLET_DATA") mins_per_block = 8.5 passwd_max_tries = 5 diff --git a/mmgen/tool.py b/mmgen/tool.py index fe30fda9..dfb976a5 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -21,31 +21,29 @@ tool.py: Routines and data for the mmgen-tool utility import sys import mmgen.bitcoin as bitcoin +import binascii as ba from mmgen.util import * from mmgen.tx import * +def Msg(s): sys.stdout.write(s + "\n") +def Msg_r(s): sys.stdout.write(s) +def Vmsg(s): + if g.verbose: sys.stdout.write(s + "\n") +def Vmsg_r(s): + if g.verbose: sys.stdout.write(s) + commands = { -# "keyconv_compare": ['wif [str]'], -# "keyconv_compare_randloop": ['iterations [int]'], -# "hextob58_pad": ['hexnum [str]], -# "b58tohex_pad": ['b58num [str]'], -# "hextob58_pad_randloop": ['iterations [int]'], -# "test_wiftohex": ['wif [str]'], -# "hextosha256": ['hexnum [str]'], -# "hextowiftopubkey": ['hexnum [str]'], -# "pubhextoaddr": ['hexnum [str]'], -# "hextowif_comp": ['hexnum [str]'], -# "wiftohex_comp": ['wif [str]'], -# "privhextoaddr_comp": ['hexnum [str]'], -# "wiftoaddr_comp": ['wif [str]'], "strtob58": [' [str]'], "hextob58": [' [str]'], "b58tohex": [' [str]'], "b58randenc": [], + "getrand": ['bytes [int=32]'], "randwif": ['compressed [bool=False]'], "randpair": ['compressed [bool=False]'], + "wif2hex": [' [str]', 'compressed [bool=False]'], "wif2addr": [' [str]', 'compressed [bool=False]'], + "hex2wif": [' [str]', 'compressed [bool=False]'], "hexdump": [' [str]', 'cols [int=8]', 'line_nums [bool=True]'], "unhexdump": [' [str]'], "mn_rand128": ['wordlist [str="electrum"]'], @@ -58,7 +56,16 @@ commands = { "listaddresses": ['minconf [int=1]', 'showempty [bool=False]'], "getbalance": ['minconf [int=1]'], "viewtx": [' [str]'], - "check_addrfile": [' [str]'] + "check_addrfile": [' [str]'], + "hexreverse": [' [str]'], + "sha256x2": [' [str]', + 'hex_input [bool=False]','file_input [bool=False]'], + "hexlify": [' [str]'], + "hexaddr2addr": [' [str]'], + "addr2hexaddr": [' [str]'], + "pubkey2addr": [' [str]'], + "pubkey2hexaddr": [' [str]'], + "privhex2addr": [' [str]','compressed [bool=False]'], } command_help = """ @@ -69,6 +76,7 @@ command_help = """ MMGen-specific operations id8 - generate 8-character MMGen ID checksum for file (or stdin) id6 - generate 6-character MMGen ID checksum for file (or stdin) + check_addrfile - compute checksum and address list for MMGen address file Bitcoin operations: strtob58 - convert a string to base 58 @@ -77,7 +85,20 @@ command_help = """ b58randenc - generate a random 32-byte number and convert it to base 58 randwif - generate a random private key in WIF format randpair - generate a random private key/address pair + wif2hex - convert a private key from WIF to hex format + hex2wif - convert a private key from hex to WIF format wif2addr - generate a Bitcoin address from a key in WIF format + pubkey2addr - convert Bitcoin public key to address + pubkey2hexaddr - convert Bitcoin public key to address in hex format + hexaddr2addr - convert Bitcoin address from hex to base58 format + addr2hexaddr - convert Bitcoin address from base58 to hex format + privhex2addr - generate Bitcoin address from private key in hex format + + Miscellaneous operations: + hexreverse - reverse bytes of a hexadecimal string + hexlify - display string in hexadecimal format + sha256x2 - compute a double sha256 hash of data + getrand - print 'n' bytes (default 32) of random data in hex format Mnemonic operations (choose "electrum" (default), "tirosh" or "all" wordlists): @@ -92,7 +113,6 @@ command_help = """ getbalance - like 'bitcoind getbalance' but shows confirmed/unconfirmed, spendable/unspendable viewtx - show raw transaction in human-readable form - check_addrfile - compute checksum and address list for MMGen address file IMPORTANT NOTE: Though MMGen mnemonics use the Electrum wordlist, they're computed using a different algorithm and are NOT Electrum-compatible! @@ -117,7 +137,6 @@ def process_args(prog_name, command, uargs): n = len(cargs_req) if len(uargs_req) != n: - print "ERROR: %s argument%s required" % (n, " is" if n==1 else "s are") tool_usage(prog_name, command) sys.exit(1) @@ -166,15 +185,16 @@ def process_args(prog_name, command, uargs): # Individual commands def print_convert_results(indata,enc,dec,no_recode=False): - vmsg("Input: [%s]" % indata) - vmsg_r("Encoded data: ["); msg_r(enc); vmsg_r("]"); msg("") + Vmsg("Input: [%s]" % indata) + Vmsg_r("Encoded data: ["); Msg_r(enc); Vmsg_r("]"); Msg("") if not no_recode: - vmsg("Recoded data: [%s]" % dec) + Vmsg("Recoded data: [%s]" % dec) if indata != dec: - msg("WARNING! Recoded number doesn't match input stringwise!") + Msg("WARNING! Recoded number doesn't match input stringwise!") def hexdump(infile, cols=8, line_nums=True): - print pretty_hexdump(get_data_from_file(infile,dash=True), 2, cols, line_nums) + print pretty_hexdump(get_data_from_file(infile,dash=True), + cols=cols, line_nums=line_nums) def unhexdump(infile): sys.stdout.write(decode_pretty_hexdump(get_data_from_file(infile,dash=True))) @@ -185,15 +205,15 @@ def strtob58(s): print_convert_results(s,enc,dec) def hextob58(s,f_enc=bitcoin.b58encode, f_dec=bitcoin.b58decode): - enc = f_enc(unhexlify(s)) - dec = hexlify(f_dec(enc)) + enc = f_enc(ba.unhexlify(s)) + dec = ba.hexlify(f_dec(enc)) print_convert_results(s,enc,dec) def b58tohex(s,f_enc=bitcoin.b58decode, f_dec=bitcoin.b58encode): tmp = f_enc(s) if tmp == False: sys.exit(1) - enc = hexlify(tmp) - dec = f_dec(unhexlify(enc)) + enc = ba.hexlify(tmp) + dec = f_dec(ba.unhexlify(enc)) print_convert_results(s,enc,dec) def get_random(length): @@ -204,28 +224,31 @@ def b58randenc(): r = get_random(32) enc = bitcoin.b58encode(r) dec = bitcoin.b58decode(enc) - print_convert_results(hexlify(r),enc,hexlify(dec)) + print_convert_results(ba.hexlify(r),enc,ba.hexlify(dec)) + +def getrand(bytes='32'): + print ba.hexlify(get_random(int(bytes))) def randwif(compressed=False): - r_hex = hexlify(get_random(32)) + r_hex = ba.hexlify(get_random(32)) enc = bitcoin.hextowif(r_hex,compressed) print_convert_results(r_hex,enc,"",no_recode=True) def randpair(compressed=False): - r_hex = hexlify(get_random(32)) + r_hex = ba.hexlify(get_random(32)) wif = bitcoin.hextowif(r_hex,compressed) addr = bitcoin.privnum2addr(int(r_hex,16),compressed) - vmsg("Key (hex): %s" % r_hex) - vmsg_r("Key (WIF): "); msg(wif) - vmsg_r("Addr: "); msg(addr) + Vmsg("Key (hex): %s" % r_hex) + Vmsg_r("Key (WIF): "); Msg(wif) + Vmsg_r("Addr: "); Msg(addr) -def wif2addr(s_in,compressed=False): - s_enc = bitcoin.wiftohex(s_in,compressed) +def wif2addr(wif,compressed=False): + s_enc = bitcoin.wiftohex(wif,compressed) if s_enc == False: - msg("Invalid address") + Msg("Invalid address") sys.exit(1) addr = bitcoin.privnum2addr(int(s_enc,16),compressed) - vmsg_r("Addr: "); msg(addr) + Vmsg_r("Addr: "); Msg(addr) from mmgen.mnemonic import * from mmgen.mn_electrum import electrum_words as el @@ -236,7 +259,7 @@ wordlists = sorted(wl_checksums.keys()) def get_wordlist(wordlist): wordlist = wordlist.lower() if wordlist not in wordlists: - msg('"%s": invalid wordlist. Valid choices: %s' % + Msg('"%s": invalid wordlist. Valid choices: %s' % (wordlist,'"'+'" "'.join(wordlists)+'"')) sys.exit(1) return el if wordlist == "electrum" else tl @@ -246,10 +269,10 @@ def do_random_mn(nbytes,wordlist): wlists = wordlists if wordlist == "all" else [wordlist] for wl in wlists: l = get_wordlist(wl) - if wl == wlists[0]: vmsg("Seed: %s" % hexlify(r)) + if wl == wlists[0]: Vmsg("Seed: %s" % ba.hexlify(r)) mn = get_mnemonic_from_seed(r,l.strip().split("\n"), wordlist,print_info=False) - vmsg("%s wordlist mnemonic:" % (wl.capitalize())) + Vmsg("%s wordlist mnemonic:" % (wl.capitalize())) print " ".join(mn) def mn_rand128(wordlist="electrum"): do_random_mn(16,wordlist) @@ -340,3 +363,37 @@ def viewtx(infile): view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata) def check_addrfile(infile): parse_addrs_file(infile) + +def hexreverse(hex_str): + print ba.hexlify(decode_pretty_hexdump(hex_str)[::-1]) + +def hexlify(s): + print ba.hexlify(s) + +def sha256x2(s, file_input=False, hex_input=False): + from hashlib import sha256 + if file_input: b = get_data_from_file(s) + elif hex_input: b = decode_pretty_hexdump(s) + else: b = s + print sha256(sha256(b).digest()).hexdigest() + +def hexaddr2addr(hexaddr): + print bitcoin.hexaddr2addr(hexaddr) + +def addr2hexaddr(addr): + print bitcoin.verify_addr(addr,return_hex=True) + +def pubkey2hexaddr(pubkeyhex): + print bitcoin.pubhex2hexaddr(pubkeyhex) + +def pubkey2addr(pubkeyhex): + print bitcoin.pubhex2addr(pubkeyhex) + +def privhex2addr(privkeyhex,compressed=False): + print bitcoin.privnum2addr(int(privkeyhex,16),compressed) + +def wif2hex(wif,compressed=False): + print bitcoin.wiftohex(wif,compressed) + +def hex2wif(hexpriv,compressed=False): + print bitcoin.hextowif(hexpriv,compressed) diff --git a/mmgen/tx.py b/mmgen/tx.py index 41027d8c..05e6dc70 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -373,14 +373,8 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False): days = int(j['confirmations'] * g.mins_per_block / (60*24)) total_in += j['amount'] addr = j['address'] - - if j['account']: - tmp = j['account'].split(None,1) - mmid,label = tmp if len(tmp) == 2 else (tmp[0],"") - label = label or "" - else: - mmid,label = "","" - + mmid,label = parse_mmgen_label(j['account']) \ + if 'account' in j else ("","") mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else "" for d in ( @@ -650,6 +644,7 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts): or "from_seed" in opts or "from_incog" in opts: msg("Need data for seed ID %s" % seed_id) seed = get_seed_retry("",opts) + msg("User input produced seed ID %s" % make_chksum_8(seed)) else: msg("ERROR: No seed source found for seed ID: %s" % seed_id) sys.exit(2) diff --git a/mmgen/util.py b/mmgen/util.py index c6aadde0..7bc2a3d3 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -279,7 +279,7 @@ def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32): return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen) -def _get_from_brain_opt_params(opts): +def get_from_brain_opt_params(opts): l,p = opts['from_brain'].split(",") return(int(l),p) @@ -287,7 +287,7 @@ def _get_from_brain_opt_params(opts): def _get_seed_from_brain_passphrase(words,opts): bp = " ".join(words) if g.debug: print "Sanitized brain passphrase: %s" % bp - seed_len,hash_preset = _get_from_brain_opt_params(opts) + seed_len,hash_preset = get_from_brain_opt_params(opts) if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset) vmsg_r("Hashing brainwallet data. Please wait...") # Use buflen arg of scrypt.hash() to get seed of desired length @@ -905,11 +905,6 @@ def get_seed(infile,opts,silent=False): if 'from_brain' not in opts: msg("'--from-brain' parameters must be specified for brainwallet file") sys.exit(2) - if not g.quiet: - confirm_or_exit( - cmessages['brain_warning'].format( - g.proj_name, *_get_from_brain_opt_params(opts)), - "continue") prompt = "Enter brainwallet passphrase: " words = _get_words(infile,"brainwallet data",prompt,opts) seed = _get_seed_from_brain_passphrase(words,opts) @@ -996,7 +991,7 @@ def export_to_hidden_incog(incog_enc,opts): "Data written to file") -def pretty_hexdump(data,gw,cols,line_nums=False): +def pretty_hexdump(data,gw=2,cols=8,line_nums=False): r = 1 if len(data) % gw else 0 return "".join( [