Browse Source

Bugfixes, new commands for 'mmgen-tool'

philemon 10 years ago
parent
commit
75ab55a2d3
9 changed files with 138 additions and 75 deletions
  1. 1 1
      mmgen-txcreate
  2. 1 1
      mmgen-txsign
  3. 8 2
      mmgen-walletgen
  4. 4 2
      mmgen/Opts.py
  5. 19 11
      mmgen/bitcoin.py
  6. 4 4
      mmgen/config.py
  7. 95 38
      mmgen/tool.py
  8. 3 8
      mmgen/tx.py
  9. 3 8
      mmgen/util.py

+ 1 - 1
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()
 

+ 1 - 1
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)

+ 8 - 2
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)
 

+ 4 - 2
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"

+ 19 - 11
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):

+ 4 - 4
mmgen/config.py

@@ -22,7 +22,7 @@ config.py:  Constants and configuration options for the mmgen suite
 author = "Philemon"
 email = "<mmgen-py@yandex.com>"
 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

+ 95 - 38
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":     ['<string> [str]'],
 	"hextob58":     ['<hex number> [str]'],
 	"b58tohex":     ['<b58 number> [str]'],
 	"b58randenc":   [],
+	"getrand":      ['bytes [int=32]'],
 	"randwif":      ['compressed [bool=False]'],
 	"randpair":     ['compressed [bool=False]'],
+	"wif2hex":      ['<wif> [str]', 'compressed [bool=False]'],
 	"wif2addr":     ['<wif> [str]', 'compressed [bool=False]'],
+	"hex2wif":      ['<private key in hex format> [str]', 'compressed [bool=False]'],
 	"hexdump":      ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
 	"unhexdump":    ['<infile> [str]'],
 	"mn_rand128":   ['wordlist [str="electrum"]'],
@@ -58,7 +56,16 @@ commands = {
 	"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
 	"getbalance":   ['minconf [int=1]'],
 	"viewtx":       ['<MMGen tx file> [str]'],
-	"check_addrfile":  ['<MMGen addr file> [str]']
+	"check_addrfile":  ['<MMGen addr file> [str]'],
+	"hexreverse":   ['<hexadecimal string> [str]'],
+	"sha256x2":     ['<str, hexstr or filename> [str]',
+					'hex_input [bool=False]','file_input [bool=False]'],
+	"hexlify":      ['<string> [str]'],
+	"hexaddr2addr": ['<btc address in hex format> [str]'],
+	"addr2hexaddr": ['<btc address> [str]'],
+	"pubkey2addr":  ['<public key in hex format> [str]'],
+	"pubkey2hexaddr": ['<public key in hex format> [str]'],
+	"privhex2addr": ['<private key in hex format> [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)

+ 3 - 8
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)

+ 3 - 8
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(
 		[